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
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
// arch/parakeet/parakeet.h - Parakeet-family internal model and context
// types: the concrete classes deriving from transcribe_model /
// transcribe_session. Internal to the Parakeet sources (also visible to
// tests/transcribe_tokenizer_smoke via the test target's PRIVATE include
// path). The central dispatcher talks to the family only through the Arch
// trait and the base classes.
#pragma once
#include "decoder.h"
#include "transcribe-backend.h"
#include "transcribe-mel.h"
#include "transcribe-model.h"
#include "transcribe-session.h"
#include "transcribe-tokenizer.h"
#include "weights.h"
#include <cstdint>
#include <optional>
#include <string>
#include <vector>
struct ggml_context;
struct ggml_tensor;
struct ggml_backend;
struct ggml_backend_buffer;
struct ggml_backend_sched;
typedef struct ggml_backend * ggml_backend_t;
typedef struct ggml_backend_buffer * ggml_backend_buffer_t;
typedef struct ggml_backend_sched * ggml_backend_sched_t;
namespace transcribe::parakeet {
// Host-side attention-mask helpers for the streaming paths. Declared
// here so the per-mask unit tests can call them without a ggml backend
// or a GGUF on disk.
//
// chunked_limited_with_rc mask (buffered streaming). Mirrors NeMo
// ConformerEncoder._create_masks:
// c_q = q / C
// window_start = max(0, c_q * C - L)
// window_end = min(T - 1, c_q * C + C - 1 + R)
// mask[q, k] = 0 if window_start <= k <= window_end else -INF
// Output is row-major [T_q, T_k] with T_q == T_k == T (>= T*T floats);
// broadcasts over heads in rel_pos_mhsa.
//
// `pad_length` (optional) folds in NeMo's conv-overhang pad_mask: cells
// where q >= pad_length OR k >= pad_length are masked regardless of the
// window. pad_length = T disables it (offline). For buffered streaming
// pass effective_T = ctx.total / encoder_frame2audio_samples — the conv
// frontend may produce T_enc > effective_T, and those trailing frames
// must be masked or they contaminate valid frames' attention scores.
void compute_chunked_limited_with_rc_mask(float * out_buf,
int T,
int left_context_frames,
int chunk_size_frames,
int right_context_frames,
int pad_length);
// Family defaults — applied before transcribe::read_capability_kv runs
// (KV present overrides, KV absent leaves the default). Defined in
// capabilities.cpp. There is no per-variant resolver: variant identity
// is descriptive metadata; v2/v3 differences are expressed as
// stt.capability.* / general.languages KV.
void apply_family_invariants(transcribe_model & model);
// Concrete model. Owns ctx_meta, which holds every weight tensor's
// data buffer; the destructor frees it, invalidating every borrowed
// ggml_tensor* in `weights`.
struct ParakeetModel final : public transcribe_model {
Tokenizer tok;
ParakeetHParams hparams;
ParakeetWeights weights;
ggml_context * ctx_meta = nullptr;
// Runtime backend plan, resolved at load() via
// load_common::init_backends (see transcribe-backend.h). plan's
// scheduler_list owns the backends; the destructor frees them in
// reverse order.
transcribe::BackendPlan plan;
ggml_backend_buffer_t backend_buffer = nullptr;
// Fused BN parameters live in a separate ggml context + buffer,
// computed at load time from the raw BN tensors. Freed in dtor.
ggml_context * bn_fused_ctx = nullptr;
ggml_backend_buffer_t bn_fused_buffer = nullptr;
// On a CPU primary backend, the conformer 1×1 pointwise conv weights
// are dequantized from F16 to F32 at load: CPUs without native F16
// arithmetic (Zen 2 and earlier) pay a per-matmul F16→F32 upconvert
// that erases the bandwidth win, so promoting once at load is cheaper.
// No-op on GPU backends and on non-CPU primary backends. The F16
// source tensors stay resident (ggml can't free individual slots);
// the weight slot is repointed at the F32 copy.
ggml_context * conv_pw_f32_ctx = nullptr;
ggml_backend_buffer_t conv_pw_f32_buffer = nullptr;
// Mel front-end, constructed once at load() from the hparams
// (precomputes Hann window + Slaney mel filterbank).
// const-after-ctor and thread-safe, so shared by every context.
// std::optional because MelFrontend has no default constructor.
std::optional<transcribe::MelFrontend> mel;
// Host mirror of the predictor + joint weights, populated at load()
// via build_host_decoder_weights. The decoder runs on host (see
// decoder.h); the const-after-load mirror lets every context share it
// without a per-run backend readback.
HostDecoderWeights host_decoder;
ParakeetModel() = default;
~ParakeetModel() override;
const transcribe::Tokenizer * tokenizer() const override { return &tok; }
};
// Per-context streaming encoder state, allocated lazily on the first
// stream_begin for ChunkedLimited variants. Mirrors NeMo's
// get_initial_cache_state tuple. Layout cheat sheet:
// cache_last_channel [d_model, T_cache, n_layer] f32 — per-layer
// post-attn-LN cache, concat-prepended to x_norm before Q/K/V.
// cache_last_time [k_minus_1, d_model, n_layer] f32 — per-layer
// post-pw1+GLU cache, replaces the depthwise conv's left zero-pad.
// cache_last_channel_len scalar — valid frames (0..T_cache); drives
// the streaming-mask offset until the cache fills.
// Sizes for nemotron-speech-streaming-en-0.6b at [70,13]: T_cache=70,
// k_minus_1=8, n_layer=24, d_model=1024 (~7.5 MB total).
struct ParakeetStreamingCaches {
ggml_context * ctx = nullptr;
ggml_backend_buffer_t buffer = nullptr;
// Per-layer cache tensors. Shapes:
// last_channel[i] = ne[d_model, T_cache, 1, 1]
// last_time[i] = ne[k_minus_1, d_model, 1, 1]
std::vector<ggml_tensor *> last_channel;
std::vector<ggml_tensor *> last_time;
// KV-cache variant of last_channel: per-layer pre-projected K/V
// (bias included) over the T_cache window, ne[d_model, T_cache, 1, 1]
// each. Equivalent to recomputing K/V from last_channel every chunk
// but the per-chunk projections run on T_q_new columns instead of
// T_cache + T_q_new. The default graph consumes these; an active dump
// harness falls back to the channel recompute path. Cleared at
// stream_begin.
std::vector<ggml_tensor *> last_k;
std::vector<ggml_tensor *> last_v;
// Rel-pos projection memoization: per-layer
// [head_dim, pos_proj_len, n_head, 1] f32 tensors holding
// attn_pos_w @ pos_emb. Geometry-keyed (pos_proj_len), so it survives
// across chunks and streams; rebuilt by ensure_pos_proj_cache on a
// pos_len change. Own ctx + buffer (size varies with geometry).
ggml_context * pos_proj_ctx = nullptr;
ggml_backend_buffer_t pos_proj_buf = nullptr;
std::vector<ggml_tensor *> pos_proj;
int pos_proj_len = -1;
int32_t channel_len = 0;
// Absolute mel-frame cursor across the whole stream, anchoring
// token/segment timestamps to original-audio time even when chunks
// vary in size. Counts mel frames consumed (minus history prepend).
int64_t mel_frames_consumed = 0;
// Absolute sample index of stream_pcm_buffer[0]. The buffer is a
// sliding window: prefix samples no longer needed by any unemitted
// frame's STFT window are trimmed (hop-aligned), bounding per-feed mel
// cost to O(new audio). Always a hop_length multiple so mel-frame
// indexing translates cleanly between absolute and buffer-relative.
int64_t pcm_start_sample = 0;
// Streaming geometry, resolved at stream_begin from ParakeetHParams +
// the caller-selected att_context_right. Constant across chunks within
// a stream. First- vs subsequent-chunk geometry differ (NeMo's
// setup_streaming_params); the driver picks the pair via
// is_first_chunk. For nemotron-speech-streaming-en at R=13:
// chunk_size_first=105, chunk_size_subsequent=112,
// mel_fed_first=105, mel_fed_subsequent=121 (9 cache + 112 new),
// drop_extra_first=0, drop_extra_subsequent=2.
int att_context_right = 0;
int att_context_left = 0;
int chunk_size_first = 0;
int chunk_size_subsequent = 0;
int mel_fed_first = 0;
int mel_fed_subsequent = 0;
int drop_extra_first = 0;
int drop_extra_subsequent = 0;
// Whether the next emit_streaming_chunk is the FIRST in this stream.
bool is_first_chunk = true;
// Per-stream chunk counter (NeMo's step_num). Resets at stream_begin,
// increments per emit; indexes dump filenames.
int chunk_step = 0;
bool initialized = false;
};
// Per-context streaming RNN-T decoder state, owned on the context so it
// carries across stream_feed calls. Reset at each stream_begin to a fresh
// start-of-sequence state.
struct ParakeetStreamingDecoderState {
// LSTM (h, c) per layer; sized at stream_begin.
LstmState lstm_state;
// Last emitted non-blank token id (predictor input); -1 = SOS.
int32_t prev_token_id = -1;
// Absolute encoder-frame offset for converting per-chunk step_at_emit
// into stream-wide frame indices.
int64_t frame_offset = 0;
bool initialized = false;
};
// Concrete context. Owns a per-call compute context and a persistent
// multi-backend scheduler that dispatches encoder graph ops to the best
// available backend.
struct ParakeetSession final : public transcribe_session {
// Compute context: cgraph + intermediate tensor metadata. no_alloc;
// data lives in sched-managed buffers. Reset each run().
ggml_context * compute_ctx = nullptr;
// Multi-backend scheduler. Persists across calls; manages compute
// buffer allocation and reuses buffers when topology is unchanged.
ggml_backend_sched_t sched = nullptr;
// Encoder forward output, borrowed into compute_ctx; invalidated when
// compute_ctx is reset next run().
ggml_tensor * encoder_out = nullptr;
// Per-context scratch reused across runs.
std::vector<float> mel_buf;
std::vector<float> pos_buf;
std::vector<float> pos_div_term;
std::vector<float> enc_host;
std::vector<TdtToken> raw_tokens;
// Per-call timings and the TDT decode result (tokens / words /
// segments / full_text / result_kind / has_result) live on the base
// transcribe_session; no per-family copies here.
// ---- streaming-of-whole state ----
//
// stream_begin captures run_params; stream_feed appends PCM;
// stream_finalize drains the buffer through the inference helper;
// stream_reset clears (keeping capacity). NOT touched by
// clear_result (the family owns its per-utterance audio scratch).
std::vector<float> stream_pcm_buffer;
transcribe_run_params stream_run_params{};
// ---- incremental streaming state (cache-aware) ----
//
// Allocated on the first stream_begin for ChunkedLimited variants,
// zeroed at each stream_begin, persists across feed/finalize within
// an utterance, freed in the dtor.
ParakeetStreamingCaches stream_caches;
ParakeetStreamingDecoderState stream_dec_state;
// ---- Buffered streaming state (parakeet-unified-en-0.6b) ----
//
// Mirrors NeMo's StreamingBatchedAudioBuffer + reference inference
// loop. Variable-stride: step 0 consumes samples_chunk + samples_right
// of new audio; subsequent feeds consume samples_chunk; finalize's
// last step consumes the rest (chunk slot absorbs the trailing right
// context, no zero-pad). The buf_* geometry reflects the active
// (L, C, R) tuple; the ctx_* fields track the buffer's internal
// ContextSize, updated per-chunk via buf_ctx_add_frames (NeMo's
// add_frames_get_removed_). The RNN-T state rides on stream_dec_state
// (same predictor/joint path, no per-layer encoder cache).
int32_t buf_left_frames = 0; // L (expected)
int32_t buf_chunk_frames = 0; // C
int32_t buf_right_frames = 0; // R
int32_t buf_samples_left = 0; // L * encoder_frame2audio_samples
int32_t buf_samples_chunk = 0;
int32_t buf_samples_right = 0;
// Next sample index in stream_pcm_buffer to feed (= NeMo's
// `left_sample` at the start of the next step). 0 at stream_begin;
// advances by num_new_samples after each emit.
int64_t buf_next_audio_read = 0;
// Buffer's internal ContextSize. (0,0,0) at stream_begin; ramps up
// as audio arrives until it saturates at (samples_left, chunk, right).
int64_t buf_ctx_left = 0;
int64_t buf_ctx_chunk = 0;
int64_t buf_ctx_right = 0;
// Whether step 0 (the initial fill of samples_chunk+samples_right)
// has run. False at stream_begin; first emit flips it.
bool buf_initialized = false;
// Per-stream chunk step counter (== NeMo's `step_num`). Used for
// per-chunk dump file naming.
int32_t buf_chunk_step = 0;
bool buf_active = false;
ParakeetSession() = default;
~ParakeetSession() override;
};
} // namespace transcribe::parakeet