#include "decoder.h"
#include "encoder.h"
#include "ggml-alloc.h"
#include "ggml-backend.h"
#include "ggml.h"
#include "gguf.h"
#include "parakeet.h"
#include "transcribe-arch.h"
#include "transcribe-batch-util.h"
#include "transcribe-debug.h"
#include "transcribe-load-common.h"
#include "transcribe-loader.h"
#include "transcribe-log.h"
#include "transcribe-mel.h"
#include "transcribe-meta.h"
#include "transcribe/parakeet.h"
#include "weights.h"
#include <algorithm>
#include <cassert>
#include <cmath>
#include <cstdint>
#include <cstdio>
#include <cstring>
#include <fstream>
#include <ios>
#include <limits>
#include <memory>
#include <set>
#include <string>
#include <thread>
#include <type_traits>
#include <utility>
#include <vector>
namespace transcribe::parakeet {
extern const Arch arch;
static_assert(std::is_base_of_v<transcribe_model, ParakeetModel>);
static_assert(std::is_base_of_v<transcribe_session, ParakeetSession>);
ParakeetSession::~ParakeetSession() {
if (sched != nullptr) {
safe_sched_free(sched);
sched = nullptr;
}
if (compute_ctx != nullptr) {
ggml_free(compute_ctx);
compute_ctx = nullptr;
}
encoder_out = nullptr;
if (stream_caches.buffer != nullptr) {
safe_buffer_free(stream_caches.buffer);
stream_caches.buffer = nullptr;
}
if (stream_caches.ctx != nullptr) {
ggml_free(stream_caches.ctx);
stream_caches.ctx = nullptr;
}
if (stream_caches.pos_proj_buf != nullptr) {
safe_buffer_free(stream_caches.pos_proj_buf);
stream_caches.pos_proj_buf = nullptr;
}
if (stream_caches.pos_proj_ctx != nullptr) {
ggml_free(stream_caches.pos_proj_ctx);
stream_caches.pos_proj_ctx = nullptr;
}
stream_caches.pos_proj.clear();
stream_caches.pos_proj_len = -1;
stream_caches.last_channel.clear();
stream_caches.last_time.clear();
stream_caches.last_k.clear();
stream_caches.last_v.clear();
stream_caches.initialized = false;
}
ParakeetModel::~ParakeetModel() {
if (bn_fused_ctx != nullptr) {
ggml_free(bn_fused_ctx);
bn_fused_ctx = nullptr;
}
if (bn_fused_buffer != nullptr) {
safe_buffer_free(bn_fused_buffer);
bn_fused_buffer = nullptr;
}
if (conv_pw_f32_ctx != nullptr) {
ggml_free(conv_pw_f32_ctx);
conv_pw_f32_ctx = nullptr;
}
if (conv_pw_f32_buffer != nullptr) {
safe_buffer_free(conv_pw_f32_buffer);
conv_pw_f32_buffer = nullptr;
}
if (ctx_meta != nullptr) {
ggml_free(ctx_meta);
ctx_meta = nullptr;
}
if (backend_buffer != nullptr) {
safe_buffer_free(backend_buffer);
backend_buffer = nullptr;
}
for (auto it = plan.scheduler_list.rbegin(); it != plan.scheduler_list.rend(); ++it) {
safe_backend_free(*it);
}
plan.scheduler_list.clear();
plan.primary = nullptr;
plan.primary_kind = transcribe::BackendKind::Unknown;
}
namespace {
constexpr float kBnEps = 1e-5f;
int32_t resolve_prompt_id(const ParakeetHParams & hp, const char * language_hint) {
if (!hp.has_prompt) {
return -1;
}
const bool empty_hint = (language_hint == nullptr) || (*language_hint == '\0');
if (empty_hint) {
if (hp.prompt_auto_id >= 0) {
return hp.prompt_auto_id;
}
if (!hp.prompt_dictionary_indices.empty()) {
return hp.prompt_dictionary_indices.front();
}
return -1;
}
for (size_t i = 0; i < hp.prompt_dictionary_locales.size(); ++i) {
if (hp.prompt_dictionary_locales[i] == language_hint) {
return hp.prompt_dictionary_indices[i];
}
}
return -1;
}
bool fill_prompt_one_hot(std::vector<float> & out,
int num_prompts,
int T_enc,
int n_batch,
const std::vector<int32_t> & prompt_ids) {
const size_t total = static_cast<size_t>(num_prompts) * static_cast<size_t>(T_enc) * static_cast<size_t>(n_batch);
out.assign(total, 0.0f);
for (int b = 0; b < n_batch; ++b) {
const int32_t pid = (b < static_cast<int>(prompt_ids.size())) ? prompt_ids[static_cast<size_t>(b)] :
(prompt_ids.empty() ? -1 : prompt_ids.front());
if (pid < 0 || pid >= num_prompts) {
return false;
}
const size_t per_utt = static_cast<size_t>(num_prompts) * static_cast<size_t>(T_enc);
const size_t utt_off = static_cast<size_t>(b) * per_utt;
for (int t = 0; t < T_enc; ++t) {
const size_t row_off = utt_off + static_cast<size_t>(t) * static_cast<size_t>(num_prompts);
out[row_off + static_cast<size_t>(pid)] = 1.0f;
}
}
return true;
}
transcribe_status fuse_batch_norm(ParakeetModel & m) {
const size_t n_blocks = m.weights.blocks.size();
if (n_blocks == 0) {
return TRANSCRIBE_OK;
}
const int64_t d = m.hparams.enc_d_model;
const size_t tensor_bytes = static_cast<size_t>(d) * sizeof(float);
const size_t ctx_size = n_blocks * 2 * ggml_tensor_overhead() + 256;
ggml_init_params params = { ctx_size, nullptr, true };
m.bn_fused_ctx = ggml_init(params);
if (m.bn_fused_ctx == nullptr) {
return TRANSCRIBE_ERR_BACKEND;
}
for (size_t i = 0; i < n_blocks; ++i) {
auto & b = m.weights.blocks[i];
b.conv_bn_fused_scale = ggml_new_tensor_1d(m.bn_fused_ctx, GGML_TYPE_F32, d);
b.conv_bn_fused_bias = ggml_new_tensor_1d(m.bn_fused_ctx, GGML_TYPE_F32, d);
}
m.bn_fused_buffer = ggml_backend_alloc_ctx_tensors(m.bn_fused_ctx, m.plan.scheduler_list.back());
if (m.bn_fused_buffer == nullptr) {
return TRANSCRIBE_ERR_BACKEND;
}
std::vector<float> bn_w(d), bn_b(d), rm(d), rv(d);
std::vector<float> fused_s(d), fused_b(d);
for (size_t i = 0; i < n_blocks; ++i) {
auto & b = m.weights.blocks[i];
ggml_backend_tensor_get(b.conv_bn_w, bn_w.data(), 0, tensor_bytes);
ggml_backend_tensor_get(b.conv_bn_b, bn_b.data(), 0, tensor_bytes);
ggml_backend_tensor_get(b.conv_bn_rm, rm.data(), 0, tensor_bytes);
ggml_backend_tensor_get(b.conv_bn_rv, rv.data(), 0, tensor_bytes);
for (int64_t c = 0; c < d; ++c) {
const float s = bn_w[c] / std::sqrt(rv[c] + kBnEps);
fused_s[c] = s;
fused_b[c] = bn_b[c] - rm[c] * s;
}
ggml_backend_tensor_set(b.conv_bn_fused_scale, fused_s.data(), 0, tensor_bytes);
ggml_backend_tensor_set(b.conv_bn_fused_bias, fused_b.data(), 0, tensor_bytes);
}
return TRANSCRIBE_OK;
}
transcribe_status promote_conv_pw_to_f32_on_cpu(ParakeetModel & m) {
std::vector<load_common::ConvPwF32Slot> slots;
slots.reserve(m.weights.blocks.size() * 2);
for (auto & b : m.weights.blocks) {
if (b.conv_pw1_w != nullptr && b.conv_pw1_w->type == GGML_TYPE_F16) {
slots.push_back({ &b.conv_pw1_w, b.conv_pw1_w });
}
if (b.conv_pw2_w != nullptr && b.conv_pw2_w->type == GGML_TYPE_F16) {
slots.push_back({ &b.conv_pw2_w, b.conv_pw2_w });
}
}
return load_common::promote_conv_pw_f16_to_f32_on_cpu(m.plan, slots, "parakeet", &m.conv_pw_f32_ctx,
&m.conv_pw_f32_buffer);
}
constexpr const char k_default_variant[] = "tdt-0.6b-v2";
transcribe_status init_streaming_caches(ParakeetSession * pc, ParakeetModel * pm) {
if (pc->stream_caches.initialized) {
return TRANSCRIBE_OK;
}
const auto & hp = pm->hparams;
const int n_layer = static_cast<int>(pm->weights.blocks.size());
const int d_model = hp.enc_d_model;
const int T_cache = hp.enc_att_context_left;
const int k_minus_1 = hp.enc_conv_context_left >= 0 ? hp.enc_conv_context_left : (hp.enc_conv_kernel - 1);
if (n_layer <= 0 || d_model <= 0 || T_cache <= 0 || k_minus_1 <= 0) {
log_msg(TRANSCRIBE_LOG_LEVEL_ERROR,
"parakeet stream init_caches: degenerate sizes "
"(n_layer=%d, d_model=%d, T_cache=%d, k-1=%d)",
n_layer, d_model, T_cache, k_minus_1);
return TRANSCRIBE_ERR_INVALID_ARG;
}
const size_t ctx_size = static_cast<size_t>(4 * n_layer + 8) * ggml_tensor_overhead();
ggml_init_params ip{};
ip.mem_size = ctx_size;
ip.mem_buffer = nullptr;
ip.no_alloc = true;
pc->stream_caches.ctx = ggml_init(ip);
if (pc->stream_caches.ctx == nullptr) {
log_msg(TRANSCRIBE_LOG_LEVEL_ERROR, "parakeet stream init_caches: ggml_init failed");
return TRANSCRIBE_ERR_OOM;
}
pc->stream_caches.last_channel.assign(n_layer, nullptr);
pc->stream_caches.last_time.assign(n_layer, nullptr);
pc->stream_caches.last_k.assign(n_layer, nullptr);
pc->stream_caches.last_v.assign(n_layer, nullptr);
for (int i = 0; i < n_layer; ++i) {
ggml_tensor * lc = ggml_new_tensor_2d(pc->stream_caches.ctx, GGML_TYPE_F32, d_model, T_cache);
ggml_tensor * lt = ggml_new_tensor_2d(pc->stream_caches.ctx, GGML_TYPE_F32, k_minus_1, d_model);
ggml_tensor * lk = ggml_new_tensor_2d(pc->stream_caches.ctx, GGML_TYPE_F32, d_model, T_cache);
ggml_tensor * lv = ggml_new_tensor_2d(pc->stream_caches.ctx, GGML_TYPE_F32, d_model, T_cache);
if (lc == nullptr || lt == nullptr || lk == nullptr || lv == nullptr) {
ggml_free(pc->stream_caches.ctx);
pc->stream_caches.ctx = nullptr;
return TRANSCRIBE_ERR_OOM;
}
char name[64];
std::snprintf(name, sizeof(name), "stream.cache.last_channel.%d", i);
ggml_set_name(lc, name);
std::snprintf(name, sizeof(name), "stream.cache.last_time.%d", i);
ggml_set_name(lt, name);
std::snprintf(name, sizeof(name), "stream.cache.last_k.%d", i);
ggml_set_name(lk, name);
std::snprintf(name, sizeof(name), "stream.cache.last_v.%d", i);
ggml_set_name(lv, name);
pc->stream_caches.last_channel[i] = lc;
pc->stream_caches.last_time[i] = lt;
pc->stream_caches.last_k[i] = lk;
pc->stream_caches.last_v[i] = lv;
}
pc->stream_caches.buffer = ggml_backend_alloc_ctx_tensors(pc->stream_caches.ctx, pm->plan.primary);
if (pc->stream_caches.buffer == nullptr) {
log_msg(TRANSCRIBE_LOG_LEVEL_ERROR, "parakeet stream init_caches: backend buffer alloc failed");
ggml_free(pc->stream_caches.ctx);
pc->stream_caches.ctx = nullptr;
return TRANSCRIBE_ERR_BACKEND;
}
pc->stream_caches.channel_len = 0;
pc->stream_caches.mel_frames_consumed = 0;
pc->stream_caches.initialized = true;
return TRANSCRIBE_OK;
}
void zero_streaming_caches(ParakeetSession * pc) {
if (pc->stream_caches.buffer != nullptr) {
ggml_backend_buffer_clear(pc->stream_caches.buffer, 0);
}
pc->stream_caches.channel_len = 0;
pc->stream_caches.mel_frames_consumed = 0;
pc->stream_caches.pcm_start_sample = 0;
}
void reset_streaming_decoder_state(ParakeetSession * pc, const ParakeetModel * pm) {
const auto & pred = pm->host_decoder.predictor;
const int n_layers = static_cast<int>(pred.lstm.size());
const int pred_hidden = pred.pred_hidden;
pc->stream_dec_state.lstm_state.reset(n_layers, pred_hidden);
pc->stream_dec_state.prev_token_id = -1;
pc->stream_dec_state.frame_offset = 0;
pc->stream_dec_state.initialized = true;
}
extern transcribe_status load(Loader &, const transcribe_model_load_params *, transcribe_model **);
extern transcribe_status init_context(transcribe_model *, const transcribe_session_params *, transcribe_session **);
extern transcribe_status run(transcribe_session *, const float *, int, const transcribe_run_params *);
transcribe_status load(Loader & loader, const transcribe_model_load_params * params, transcribe_model ** out_model) {
const int64_t t_load_start = ggml_time_us();
auto m = std::make_unique<ParakeetModel>();
m->arch = &arch;
m->t_load_us = 0;
if (loader.variant().empty()) {
m->variant = k_default_variant;
} else {
m->variant = loader.variant();
}
m->backend.clear();
apply_family_invariants(*m);
m->caps.n_languages = 0;
m->caps.languages = nullptr;
if (const transcribe_status st = read_capability_kv(loader.gguf(), m->caps); st != TRANSCRIBE_OK) {
return st;
}
if (const transcribe_status st = read_languages_kv(loader.gguf(), *m); st != TRANSCRIBE_OK) {
return st;
}
if (const transcribe_status st = m->tok.load(loader.gguf()); st != TRANSCRIBE_OK) {
return st;
}
if (const transcribe_status st = read_parakeet_hparams(loader.gguf(), m->hparams); st != TRANSCRIBE_OK) {
return st;
}
const bool cache_aware_streaming =
(m->hparams.enc_att_context_style == ParakeetHParams::AttContextStyle::ChunkedLimited) &&
m->hparams.enc_att_context_left >= 0 && m->hparams.enc_att_context_right >= 0;
const bool buffered_streaming =
(m->hparams.enc_att_context_style == ParakeetHParams::AttContextStyle::ChunkedLimitedWithRc) &&
!m->hparams.enc_att_chunk_left_choices.empty() && !m->hparams.enc_att_chunk_chunk_choices.empty() &&
!m->hparams.enc_att_chunk_right_choices.empty();
if (buffered_streaming || cache_aware_streaming) {
m->caps.supports_streaming = true;
}
{
transcribe::MelConfig cfg{};
cfg.sample_rate = m->hparams.fe_sample_rate;
cfg.num_mels = m->hparams.fe_num_mels;
cfg.n_fft = m->hparams.fe_n_fft;
cfg.win_length = m->hparams.fe_win_length;
cfg.hop_length = m->hparams.fe_hop_length;
cfg.pre_emphasis = m->hparams.fe_pre_emphasis;
cfg.f_min = m->hparams.fe_f_min;
cfg.f_max = m->hparams.fe_f_max;
cfg.normalize = m->hparams.fe_normalize;
cfg.pad_mode = "constant";
m->mel.emplace(cfg);
}
gguf_init_params init_params{};
init_params.no_alloc = true;
init_params.ctx = &m->ctx_meta;
gguf_context * gguf_data = gguf_init_from_file(loader.path().c_str(), init_params);
if (gguf_data == nullptr) {
return TRANSCRIBE_ERR_GGUF;
}
if (const transcribe_status st = build_parakeet_weights(m->ctx_meta, m->hparams, m->weights); st != TRANSCRIBE_OK) {
gguf_free(gguf_data);
return st;
}
const transcribe_backend_request backend_req = (params != nullptr) ? params->backend : TRANSCRIBE_BACKEND_AUTO;
if (const transcribe_status st = transcribe::load_common::init_backends(
backend_req, (params != nullptr) ? params->gpu_device : 0, "parakeet", m->plan);
st != TRANSCRIBE_OK) {
gguf_free(gguf_data);
return st;
}
m->backend = ggml_backend_name(m->plan.primary);
m->primary_backend = m->plan.primary;
ggml_backend_buffer_t weights_buffer = ggml_backend_alloc_ctx_tensors(m->ctx_meta, m->plan.primary);
if (weights_buffer == nullptr) {
gguf_free(gguf_data);
log_msg(TRANSCRIBE_LOG_LEVEL_ERROR, "parakeet: ggml_backend_alloc_ctx_tensors failed");
return TRANSCRIBE_ERR_GGUF;
}
m->backend_buffer = weights_buffer;
ggml_backend_buffer_set_usage(weights_buffer, GGML_BACKEND_BUFFER_USAGE_WEIGHTS);
if (const transcribe_status st =
transcribe::load_common::stream_tensor_data(loader.path(), gguf_data, m->ctx_meta, "parakeet");
st != TRANSCRIBE_OK) {
gguf_free(gguf_data);
return st;
}
gguf_free(gguf_data);
if (m->hparams.enc_conv_norm_type == ParakeetHParams::ConvNormType::BatchNorm) {
if (const transcribe_status st = fuse_batch_norm(*m); st != TRANSCRIBE_OK) {
return st;
}
}
if (const transcribe_status st = promote_conv_pw_to_f32_on_cpu(*m); st != TRANSCRIBE_OK) {
return st;
}
if (const transcribe_status st = build_host_decoder_weights(*m, m->host_decoder); st != TRANSCRIBE_OK) {
return st;
}
m->t_load_us = ggml_time_us() - t_load_start;
*out_model = m.release();
return TRANSCRIBE_OK;
}
transcribe_status init_context(transcribe_model * model,
const transcribe_session_params * params,
transcribe_session ** out_ctx) {
if (model->arch != &arch) {
return TRANSCRIBE_ERR_INVALID_ARG;
}
auto pc = std::make_unique<ParakeetSession>();
pc->model = model;
pc->n_threads = params->n_threads;
pc->kv_type = params->kv_type;
*out_ctx = pc.release();
return TRANSCRIBE_OK;
}
static bool is_lang_tag_piece(const std::string & p) {
const size_t n = p.size();
if (n < 7 || p.front() != '<' || p.back() != '>') {
return false; }
size_t i = 1;
const size_t end = n - 1;
const size_t lang0 = i;
while (i < end && p[i] >= 'a' && p[i] <= 'z') {
++i;
}
const size_t lang_len = i - lang0;
if (lang_len < 2 || lang_len > 3) {
return false;
}
if (i >= end || p[i] != '-') {
return false;
}
++i;
const size_t reg0 = i;
while (i < end && std::isalpha(static_cast<unsigned char>(p[i]))) {
++i;
}
const size_t reg_len = i - reg0;
if (reg_len < 2 || reg_len > 4) {
return false;
}
return i == end; }
static bool is_strippable_special(const transcribe::Tokenizer & tok, int id) {
return tok.is_control(id) || is_lang_tag_piece(tok.token(id));
}
static void normalize_transcript_whitespace(std::string & s) {
std::string norm;
norm.reserve(s.size());
bool prev_space = false;
for (char ch : s) {
const bool is_space = (ch == ' ');
if (is_space && prev_space) {
continue;
}
norm.push_back(ch);
prev_space = is_space;
}
while (!norm.empty() && norm.front() == ' ') {
norm.erase(norm.begin());
}
while (!norm.empty() && norm.back() == ' ') {
norm.pop_back();
}
s.swap(norm);
}
static double parakeet_ms_per_enc_frame(const ParakeetHParams & hp) {
return 1000.0 * static_cast<double>(hp.enc_subsampling_factor) * static_cast<double>(hp.fe_hop_length) /
static_cast<double>(hp.fe_sample_rate);
}
static transcribe_status decode_and_populate(ParakeetSession * pc,
ParakeetModel * pm,
const transcribe_run_params * params,
const float * enc,
int T_enc,
int d_enc,
int utt_index,
const char * enc_dump_name_override = nullptr) {
std::string dump_name = (enc_dump_name_override != nullptr && *enc_dump_name_override != '\0') ?
std::string(enc_dump_name_override) :
std::string("dec.enc_out");
if (utt_index >= 0) {
dump_name += ".b" + std::to_string(utt_index);
}
if (transcribe::debug::enabled()) {
const long long shape[2] = { T_enc, d_enc };
const char * stage = (enc_dump_name_override != nullptr && *enc_dump_name_override != '\0') ?
"decoder.enc_out_prompted" :
"decoder.enc_out";
transcribe::debug::dump_host_f32(
dump_name.c_str(), enc, static_cast<long long>(T_enc) * static_cast<long long>(d_enc), shape, 2, stage);
}
pc->raw_tokens.clear();
const int64_t t_dec_start = ggml_time_us();
{
transcribe_status st = TRANSCRIBE_OK;
switch (pm->host_decoder.head_kind) {
case HostHeadKind::TDT:
st = decode_tdt_greedy(pm->host_decoder, enc, T_enc, d_enc, pc->n_threads, pc->raw_tokens);
break;
case HostHeadKind::RNNT:
st = decode_rnnt_greedy(pm->host_decoder, enc, T_enc, d_enc, pc->n_threads, pc->raw_tokens);
break;
case HostHeadKind::CTC:
st = decode_ctc_greedy(pm->host_decoder, enc, T_enc, d_enc, pc->n_threads, pc->raw_tokens);
break;
}
if (st != TRANSCRIBE_OK) {
return st;
}
}
pc->t_decode_us = ggml_time_us() - t_dec_start;
const double frame_to_ms = parakeet_ms_per_enc_frame(pm->hparams);
constexpr const char k_sp_marker[] = "\xE2\x96\x81";
constexpr int k_sp_marker_len = 3;
const transcribe::Tokenizer & tok = pm->tok;
pc->tokens.reserve(pc->raw_tokens.size());
const bool strip_tags = (params == nullptr) ? true : !params->keep_special_tags;
for (const TdtToken & rt : pc->raw_tokens) {
if (strip_tags && is_strippable_special(tok, rt.id)) {
continue;
}
transcribe_session::TokenEntry te;
te.id = rt.id;
te.p = rt.p;
te.t0_ms = static_cast<int64_t>(std::llround(frame_to_ms * static_cast<double>(rt.step_at_emit)));
const double end_frame = static_cast<double>(rt.step_at_emit) + static_cast<double>(rt.duration_frames);
te.t1_ms = static_cast<int64_t>(std::llround(frame_to_ms * end_frame));
te.text = tok.decode(&rt.id, 1);
pc->tokens.push_back(std::move(te));
}
if (!pc->tokens.empty()) {
transcribe_session::SegmentEntry seg;
seg.t0_ms = pc->tokens.front().t0_ms;
seg.t1_ms = pc->tokens.back().t1_ms;
seg.first_token = 0;
seg.n_tokens = static_cast<int>(pc->tokens.size());
seg.first_word = 0;
transcribe_session::WordEntry cur_word;
bool cur_word_open = false;
auto open_new_word = [&](int token_index, const transcribe_session::TokenEntry & tk) {
if (cur_word_open) {
cur_word.t1_ms = pc->tokens[static_cast<size_t>(token_index - 1)].t1_ms;
cur_word.n_tokens = token_index - cur_word.first_token;
pc->words.push_back(std::move(cur_word));
cur_word = transcribe_session::WordEntry{};
}
cur_word.t0_ms = tk.t0_ms;
cur_word.first_token = token_index;
cur_word.seg_index = 0;
cur_word_open = true;
};
for (size_t i = 0; i < pc->tokens.size(); ++i) {
const auto & tk = pc->tokens[i];
const auto & raw_piece = tok.token(tk.id); const bool starts_word = (i == 0) || (raw_piece.size() >= static_cast<size_t>(k_sp_marker_len) &&
std::memcmp(raw_piece.data(), k_sp_marker, k_sp_marker_len) == 0);
if (starts_word) {
open_new_word(static_cast<int>(i), tk);
}
pc->tokens[i].seg_index = 0;
pc->tokens[i].word_index = static_cast<int>(pc->words.size());
}
if (cur_word_open) {
cur_word.t1_ms = pc->tokens.back().t1_ms;
cur_word.n_tokens = static_cast<int>(pc->tokens.size()) - cur_word.first_token;
pc->words.push_back(std::move(cur_word));
}
std::vector<int> id_buf;
for (auto & wd : pc->words) {
id_buf.clear();
id_buf.reserve(static_cast<size_t>(wd.n_tokens));
for (int j = 0; j < wd.n_tokens; ++j) {
id_buf.push_back(pc->tokens[static_cast<size_t>(wd.first_token + j)].id);
}
std::string text = tok.decode(id_buf.data(), wd.n_tokens);
if (!text.empty() && text.front() == ' ') {
text.erase(text.begin());
}
wd.text = std::move(text);
}
seg.n_words = static_cast<int>(pc->words.size());
std::vector<int> all_ids;
all_ids.reserve(pc->tokens.size());
for (const auto & tk : pc->tokens) {
all_ids.push_back(tk.id);
}
std::string full = tok.decode(all_ids.data(), static_cast<int>(all_ids.size()));
normalize_transcript_whitespace(full);
seg.text = full;
pc->full_text = std::move(full);
pc->segments.push_back(std::move(seg));
transcribe_timestamp_kind eff = params->timestamps;
if (eff == TRANSCRIBE_TIMESTAMPS_AUTO) {
eff = pm->caps.max_timestamp_kind; }
if (eff == TRANSCRIBE_TIMESTAMPS_NONE) {
pc->tokens.clear();
pc->words.clear();
auto & s = pc->segments.back();
s.t0_ms = 0;
s.t1_ms = 0;
s.first_word = 0;
s.n_words = 0;
s.first_token = 0;
s.n_tokens = 0;
} else if (eff == TRANSCRIBE_TIMESTAMPS_SEGMENT) {
pc->tokens.clear();
pc->words.clear();
auto & s = pc->segments.back();
s.first_word = 0;
s.n_words = 0;
s.first_token = 0;
s.n_tokens = 0;
} else if (eff == TRANSCRIBE_TIMESTAMPS_WORD) {
pc->tokens.clear();
for (auto & w : pc->words) {
w.first_token = 0;
w.n_tokens = 0;
}
auto & s = pc->segments.back();
s.first_token = 0;
s.n_tokens = 0;
}
pc->result_kind = eff;
pc->has_result = true;
}
return TRANSCRIBE_OK;
}
static transcribe_status run_one_shot_inner(ParakeetSession * pc,
ParakeetModel * pm,
const float * pcm,
int n_samples,
const transcribe_run_params * params) {
if (pc->poll_abort()) {
return TRANSCRIBE_ERR_ABORTED;
}
transcribe::debug::init();
if (!pm->mel.has_value()) {
log_msg(TRANSCRIBE_LOG_LEVEL_ERROR, "parakeet run: model has no MelFrontend (load skipped?)");
return TRANSCRIBE_ERR_INVALID_ARG;
}
const int64_t t_mel_start = ggml_time_us();
int mel_n_mels = 0;
int mel_n_frames = 0;
if (const transcribe_status mst =
pm->mel->compute(pcm, static_cast<size_t>(n_samples), pc->mel_buf, mel_n_mels, mel_n_frames);
mst != TRANSCRIBE_OK) {
log_msg(TRANSCRIBE_LOG_LEVEL_ERROR, "parakeet run: MelFrontend::compute failed (%s)",
transcribe_status_string(mst));
return mst;
}
pc->t_mel_us = ggml_time_us() - t_mel_start;
if (pc->compute_ctx != nullptr) {
ggml_free(pc->compute_ctx);
pc->compute_ctx = nullptr;
}
pc->encoder_out = nullptr;
{
ggml_init_params init_params{};
init_params.mem_size = 4 * 1024 * 1024;
init_params.mem_buffer = nullptr;
init_params.no_alloc = true;
pc->compute_ctx = ggml_init(init_params);
if (pc->compute_ctx == nullptr) {
log_msg(TRANSCRIBE_LOG_LEVEL_ERROR, "parakeet run: ggml_init for compute_ctx failed");
return TRANSCRIBE_ERR_GGUF;
}
}
ggml_type resolved_kv = GGML_TYPE_COUNT;
if (pc->kv_type == TRANSCRIBE_KV_TYPE_F32) {
resolved_kv = GGML_TYPE_F32;
}
if (pc->kv_type == TRANSCRIBE_KV_TYPE_F16) {
resolved_kv = GGML_TYPE_F16;
}
EncoderBuild eb =
build_encoder_graph(pc->compute_ctx, pm->weights, pm->hparams, mel_n_frames, resolved_kv, pm->backend.c_str());
if (eb.mel_in == nullptr || eb.out == nullptr || eb.graph == nullptr) {
return TRANSCRIBE_ERR_GGUF; }
if (pc->sched == nullptr) {
pc->sched = ggml_backend_sched_new(pm->plan.scheduler_list.data(), nullptr,
static_cast<int>(pm->plan.scheduler_list.size()),
8192, false, true);
if (pc->sched == nullptr) {
log_msg(TRANSCRIBE_LOG_LEVEL_ERROR, "parakeet run: ggml_backend_sched_new failed");
return TRANSCRIBE_ERR_GGUF;
}
}
ggml_backend_sched_reset(pc->sched);
if (!ggml_backend_sched_alloc_graph(pc->sched, eb.graph)) {
log_msg(TRANSCRIBE_LOG_LEVEL_ERROR, "parakeet run: ggml_backend_sched_alloc_graph failed");
return TRANSCRIBE_ERR_GGUF;
}
ggml_backend_tensor_set(eb.mel_in, pc->mel_buf.data(), 0, pc->mel_buf.size() * sizeof(float));
transcribe::debug::dump_tensor("enc.mel.in", eb.mel_in, "encoder.mel");
if (eb.prompt_one_hot_in != nullptr) {
const int P = static_cast<int>(eb.prompt_one_hot_in->ne[0]);
const int T_oh = static_cast<int>(eb.prompt_one_hot_in->ne[1]);
const char * lang_hint = (params != nullptr) ? params->language : nullptr;
const int32_t pid = resolve_prompt_id(pm->hparams, lang_hint);
if (pid < 0) {
log_msg(TRANSCRIBE_LOG_LEVEL_ERROR,
"parakeet run: language %s%s%s not in prompt "
"dictionary",
lang_hint ? "\"" : "", lang_hint ? lang_hint : "<null>", lang_hint ? "\"" : "");
return TRANSCRIBE_ERR_UNSUPPORTED_LANGUAGE;
}
std::vector<float> one_hot_buf;
if (!fill_prompt_one_hot(one_hot_buf, P, T_oh, 1, { pid })) {
log_msg(TRANSCRIBE_LOG_LEVEL_ERROR,
"parakeet run: prompt_id %d out of range "
"[0, %d)",
pid, P);
return TRANSCRIBE_ERR_GGUF;
}
ggml_backend_tensor_set(eb.prompt_one_hot_in, one_hot_buf.data(), 0, one_hot_buf.size() * sizeof(float));
}
if (eb.pos_emb_in != nullptr) {
const int d_model = pm->hparams.enc_d_model;
const int pos_len = static_cast<int>(eb.pos_emb_in->ne[1]);
const bool is_chunked = (pm->hparams.enc_att_context_style == ParakeetHParams::AttContextStyle::ChunkedLimited);
const bool is_local_pe =
(!is_chunked) && (pm->hparams.enc_att_context_left >= 0 && pm->hparams.enc_att_context_right >= 0);
const int zero_index = is_local_pe ? pm->hparams.enc_att_context_left : (pos_len - 1) / 2;
pc->pos_buf.assign(static_cast<size_t>(pos_len) * d_model, 0.0f);
pc->pos_div_term.resize(static_cast<size_t>(d_model / 2));
const float ln_10000 = std::log(10000.0f);
for (int k = 0; k < d_model / 2; ++k) {
pc->pos_div_term[static_cast<size_t>(k)] =
std::exp(static_cast<float>(2 * k) * (-ln_10000 / static_cast<float>(d_model)));
}
for (int i = 0; i < pos_len; ++i) {
const float pos = static_cast<float>(zero_index - i);
float * row = pc->pos_buf.data() + static_cast<size_t>(i) * d_model;
for (int k = 0; k < d_model / 2; ++k) {
const float div = pc->pos_div_term[static_cast<size_t>(k)];
row[2 * k] = std::sin(pos * div);
row[2 * k + 1] = std::cos(pos * div);
}
}
ggml_backend_tensor_set(eb.pos_emb_in, pc->pos_buf.data(), 0, pc->pos_buf.size() * sizeof(float));
transcribe::debug::dump_tensor("enc.pos_emb", eb.pos_emb_in, "encoder.pos_emb");
}
if (eb.chunked_mask_in != nullptr) {
const int T_enc = static_cast<int>(eb.chunked_mask_in->ne[0]);
const int chunk_size = pm->hparams.enc_att_context_right + 1;
const int left_chunks = (chunk_size > 0) ? (pm->hparams.enc_att_context_left / chunk_size) : 0;
std::vector<float> mask_buf(static_cast<size_t>(T_enc) * static_cast<size_t>(T_enc));
for (int q = 0; q < T_enc; ++q) {
const int q_chunk = (chunk_size > 0) ? (q / chunk_size) : 0;
const int k_min_chunk = (q_chunk - left_chunks > 0) ? (q_chunk - left_chunks) : 0;
const int k_min = k_min_chunk * chunk_size;
const int k_max = (q_chunk + 1) * chunk_size; float * row = mask_buf.data() + static_cast<size_t>(q) * T_enc;
for (int k = 0; k < T_enc; ++k) {
row[k] = (k >= k_min && k < k_max) ? 0.0f : -std::numeric_limits<float>::infinity();
}
}
ggml_backend_tensor_set(eb.chunked_mask_in, mask_buf.data(), 0, mask_buf.size() * sizeof(float));
transcribe::debug::dump_tensor("enc.attn.chunked_mask", eb.chunked_mask_in, "encoder.attn.chunked_mask");
}
transcribe::configure_sched_n_threads(pc->sched, pc->n_threads);
const int64_t t_enc_start = ggml_time_us();
if (const ggml_status gs = ggml_backend_sched_graph_compute(pc->sched, eb.graph); gs != GGML_STATUS_SUCCESS) {
log_msg(TRANSCRIBE_LOG_LEVEL_ERROR, "parakeet run: ggml_backend_sched_graph_compute failed (%d)",
static_cast<int>(gs));
return TRANSCRIBE_ERR_GGUF;
}
pc->t_encode_us = ggml_time_us() - t_enc_start;
pc->t_decode_us = 0;
auto try_dump = [](const char * name, ggml_tensor * t, const char * stage) {
if (t != nullptr) {
transcribe::debug::dump_tensor(name, t, stage);
}
};
try_dump("enc.pre_encode.out", eb.dumps.pre_encode_out, "encoder.pre_encode");
try_dump("enc.block.0.ff1", eb.dumps.block0_after_ff1, "encoder.block0.ff1");
try_dump("enc.block.0.attn", eb.dumps.block0_after_attn, "encoder.block0.attn");
try_dump("enc.block.0.conv", eb.dumps.block0_after_conv, "encoder.block0.conv");
try_dump("enc.block.0.ff2", eb.dumps.block0_after_ff2, "encoder.block0.ff2");
try_dump("enc.block.0.out", eb.dumps.block0_out, "encoder.block0.out");
if (eb.dumps.mid_block_out != nullptr && eb.dumps.mid_block_idx >= 0) {
char name[64];
std::snprintf(name, sizeof(name), "enc.block.%d.out", eb.dumps.mid_block_idx);
transcribe::debug::dump_tensor(name, eb.dumps.mid_block_out, "encoder.block.mid.out");
}
if (eb.dumps.last_block_out != nullptr && eb.dumps.last_block_idx >= 0) {
char name[64];
std::snprintf(name, sizeof(name), "enc.block.%d.out", eb.dumps.last_block_idx);
transcribe::debug::dump_tensor(name, eb.dumps.last_block_out, "encoder.block.last.out");
}
if (transcribe::debug::dump_all_blocks_requested()) {
for (size_t i = 0; i < eb.dumps.all_block_outs.size(); ++i) {
ggml_tensor * t = eb.dumps.all_block_outs[i];
if (t == nullptr) {
continue;
}
char name[64];
std::snprintf(name, sizeof(name), "enc.block.%zu.out", i);
transcribe::debug::dump_tensor(name, t, "encoder.block.bisect");
}
}
for (const auto & p : eb.dumps.sub_block_dumps) {
if (p.second == nullptr) {
continue;
}
transcribe::debug::dump_tensor(p.first.c_str(), p.second, "encoder.block.subblock");
}
try_dump("enc.final", eb.dumps.final_out, "encoder.final");
try_dump("enc.prompted", eb.dumps.prompted_out, "encoder.prompted");
pc->encoder_out = eb.out;
const int d_enc = static_cast<int>(eb.out->ne[0]);
const int T_enc = static_cast<int>(eb.out->ne[1]);
if (d_enc <= 0 || T_enc <= 0) {
log_msg(TRANSCRIBE_LOG_LEVEL_ERROR,
"parakeet run: encoder output has degenerate shape "
"[%d, %d]",
d_enc, T_enc);
return TRANSCRIBE_ERR_GGUF;
}
pc->enc_host.resize(static_cast<size_t>(d_enc) * static_cast<size_t>(T_enc));
ggml_backend_tensor_get(eb.out, pc->enc_host.data(), 0, pc->enc_host.size() * sizeof(float));
if (pm->hparams.has_prompt && eb.dumps.final_out != nullptr && transcribe::debug::enabled()) {
std::vector<float> unprompted(pc->enc_host.size());
ggml_backend_tensor_get(eb.dumps.final_out, unprompted.data(), 0, unprompted.size() * sizeof(float));
const long long shape[2] = { T_enc, d_enc };
transcribe::debug::dump_host_f32("dec.enc_out", unprompted.data(),
static_cast<long long>(T_enc) * static_cast<long long>(d_enc), shape, 2,
"decoder.enc_out");
}
const char * enc_dump_name = pm->hparams.has_prompt ? "dec.enc_out_prompted" : nullptr;
return decode_and_populate(pc, pm, params, pc->enc_host.data(), T_enc, d_enc, -1, enc_dump_name);
}
transcribe_status run(transcribe_session * session,
const float * pcm,
int n_samples,
const transcribe_run_params * params) {
if (session == nullptr || pcm == nullptr || n_samples <= 0) {
return TRANSCRIBE_ERR_INVALID_ARG;
}
auto * pc = static_cast<ParakeetSession *>(session);
auto * pm = static_cast<ParakeetModel *>(pc->model);
if (pm == nullptr || pm->plan.scheduler_list.empty()) {
return TRANSCRIBE_ERR_INVALID_ARG;
}
pc->clear_result();
return run_one_shot_inner(pc, pm, pcm, n_samples, params);
}
static int pre_encode_t_out(int in) {
return (in - 1) / 2 + 1;
}
static transcribe_status run_batch_encode(ParakeetSession * pc,
ParakeetModel * pm,
const std::vector<std::vector<float>> & mels,
const std::vector<int> & nf,
int n_mels,
int T_max,
int64_t total_mel_us,
const transcribe_run_params * params) {
const int n = static_cast<int>(mels.size());
bool var_len = false;
for (int b = 0; b < n; ++b) {
if (nf[b] != T_max) {
var_len = true;
break;
}
}
transcribe::pack_pad_channel_major(pc->mel_buf, mels, nf, n_mels, T_max);
if (pc->compute_ctx != nullptr) {
ggml_free(pc->compute_ctx);
pc->compute_ctx = nullptr;
}
pc->encoder_out = nullptr;
{
ggml_init_params init_params{};
init_params.mem_size = 4 * 1024 * 1024;
init_params.mem_buffer = nullptr;
init_params.no_alloc = true;
pc->compute_ctx = ggml_init(init_params);
if (pc->compute_ctx == nullptr) {
return TRANSCRIBE_ERR_GGUF;
}
}
ggml_type resolved_kv = GGML_TYPE_COUNT;
if (pc->kv_type == TRANSCRIBE_KV_TYPE_F32) {
resolved_kv = GGML_TYPE_F32;
}
if (pc->kv_type == TRANSCRIBE_KV_TYPE_F16) {
resolved_kv = GGML_TYPE_F16;
}
EncoderBuild eb = build_encoder_graph(pc->compute_ctx, pm->weights, pm->hparams, T_max, resolved_kv,
pm->backend.c_str(), nullptr,
n, var_len);
if (eb.mel_in == nullptr || eb.out == nullptr || eb.graph == nullptr) {
return TRANSCRIBE_ERR_GGUF;
}
if (pc->sched == nullptr) {
pc->sched = ggml_backend_sched_new(pm->plan.scheduler_list.data(), nullptr,
static_cast<int>(pm->plan.scheduler_list.size()),
8192, false, true);
if (pc->sched == nullptr) {
return TRANSCRIBE_ERR_GGUF;
}
}
ggml_backend_sched_reset(pc->sched);
if (!ggml_backend_sched_alloc_graph(pc->sched, eb.graph)) {
return TRANSCRIBE_ERR_GGUF;
}
ggml_backend_tensor_set(eb.mel_in, pc->mel_buf.data(), 0, pc->mel_buf.size() * sizeof(float));
const int d_enc = static_cast<int>(eb.out->ne[0]);
const int T_enc = static_cast<int>(eb.out->ne[1]);
if (d_enc <= 0 || T_enc <= 0) {
return TRANSCRIBE_ERR_GGUF;
}
std::vector<int> real_tenc(static_cast<size_t>(n), T_enc);
if (var_len) {
for (int b = 0; b < n; ++b) {
int t = nf[b];
t = pre_encode_t_out(t);
t = pre_encode_t_out(t);
t = pre_encode_t_out(t);
real_tenc[b] = std::min(t, T_enc);
}
transcribe::fill_keypad_mask(eb.attn_pad_mask_in, real_tenc, T_enc, n);
transcribe::fill_valid_frame_mask(eb.conv_pad_mask_in, real_tenc, T_enc, n);
auto fill_pe_mask = [&](ggml_tensor * mask, int n_down) {
if (mask == nullptr) {
return;
}
const int H = static_cast<int>(mask->ne[1]);
std::vector<float> mb(static_cast<size_t>(H) * n, 0.0f);
for (int b = 0; b < n; ++b) {
int v = nf[b];
for (int d = 0; d < n_down; ++d) {
v = pre_encode_t_out(v);
}
if (v > H) {
v = H;
}
for (int h = 0; h < v; ++h) {
mb[static_cast<size_t>(b) * H + h] = 1.0f;
}
}
ggml_backend_tensor_set(mask, mb.data(), 0, mb.size() * sizeof(float));
};
fill_pe_mask(eb.pre_encode_mask_s1_in, 1); fill_pe_mask(eb.pre_encode_mask_s2_in, 2); fill_pe_mask(eb.pre_encode_mask_s3_in, 3); }
if (eb.pos_emb_in != nullptr) {
const int d_model = pm->hparams.enc_d_model;
const int pos_len = static_cast<int>(eb.pos_emb_in->ne[1]);
const bool is_chunked = (pm->hparams.enc_att_context_style == ParakeetHParams::AttContextStyle::ChunkedLimited);
const bool is_local_pe =
(!is_chunked) && (pm->hparams.enc_att_context_left >= 0 && pm->hparams.enc_att_context_right >= 0);
const int zero_index = is_local_pe ? pm->hparams.enc_att_context_left : (pos_len - 1) / 2;
pc->pos_buf.assign(static_cast<size_t>(pos_len) * d_model, 0.0f);
pc->pos_div_term.resize(static_cast<size_t>(d_model / 2));
const float ln_10000 = std::log(10000.0f);
for (int k = 0; k < d_model / 2; ++k) {
pc->pos_div_term[static_cast<size_t>(k)] =
std::exp(static_cast<float>(2 * k) * (-ln_10000 / static_cast<float>(d_model)));
}
for (int i = 0; i < pos_len; ++i) {
const float pos = static_cast<float>(zero_index - i);
float * row = pc->pos_buf.data() + static_cast<size_t>(i) * d_model;
for (int k = 0; k < d_model / 2; ++k) {
const float div = pc->pos_div_term[static_cast<size_t>(k)];
row[2 * k] = std::sin(pos * div);
row[2 * k + 1] = std::cos(pos * div);
}
}
ggml_backend_tensor_set(eb.pos_emb_in, pc->pos_buf.data(), 0, pc->pos_buf.size() * sizeof(float));
}
if (eb.prompt_one_hot_in != nullptr) {
const int P = static_cast<int>(eb.prompt_one_hot_in->ne[0]);
const int T_oh = static_cast<int>(eb.prompt_one_hot_in->ne[1]);
const char * lang_hint = (params != nullptr) ? params->language : nullptr;
const int32_t pid = resolve_prompt_id(pm->hparams, lang_hint);
if (pid < 0) {
log_msg(TRANSCRIBE_LOG_LEVEL_ERROR,
"parakeet run_batch: language %s%s%s not in prompt "
"dictionary",
lang_hint ? "\"" : "", lang_hint ? lang_hint : "<null>", lang_hint ? "\"" : "");
return TRANSCRIBE_ERR_UNSUPPORTED_LANGUAGE;
}
std::vector<int32_t> pids(static_cast<size_t>(n), pid);
std::vector<float> one_hot_buf;
if (!fill_prompt_one_hot(one_hot_buf, P, T_oh, n, pids)) {
log_msg(TRANSCRIBE_LOG_LEVEL_ERROR,
"parakeet run_batch: prompt_id %d out of range "
"[0, %d)",
pid, P);
return TRANSCRIBE_ERR_GGUF;
}
ggml_backend_tensor_set(eb.prompt_one_hot_in, one_hot_buf.data(), 0, one_hot_buf.size() * sizeof(float));
}
if (eb.chunked_mask_in != nullptr) {
const int Tk = static_cast<int>(eb.chunked_mask_in->ne[0]);
const int chunk_size = pm->hparams.enc_att_context_right + 1;
const int left_chunks = (chunk_size > 0) ? (pm->hparams.enc_att_context_left / chunk_size) : 0;
std::vector<float> mask_buf(static_cast<size_t>(Tk) * Tk);
for (int q = 0; q < Tk; ++q) {
const int q_chunk = (chunk_size > 0) ? (q / chunk_size) : 0;
const int k_min_chunk = (q_chunk - left_chunks > 0) ? (q_chunk - left_chunks) : 0;
const int k_min = k_min_chunk * chunk_size;
const int k_max = (q_chunk + 1) * chunk_size;
float * row = mask_buf.data() + static_cast<size_t>(q) * Tk;
for (int k = 0; k < Tk; ++k) {
row[k] = (k >= k_min && k < k_max) ? 0.0f : -std::numeric_limits<float>::infinity();
}
}
ggml_backend_tensor_set(eb.chunked_mask_in, mask_buf.data(), 0, mask_buf.size() * sizeof(float));
}
transcribe::configure_sched_n_threads(pc->sched, pc->n_threads);
const int64_t t_enc_start = ggml_time_us();
if (const ggml_status gs = ggml_backend_sched_graph_compute(pc->sched, eb.graph); gs != GGML_STATUS_SUCCESS) {
log_msg(TRANSCRIBE_LOG_LEVEL_ERROR, "parakeet run_batch: graph_compute failed (%d)", static_cast<int>(gs));
return TRANSCRIBE_ERR_GGUF;
}
pc->t_encode_us = ggml_time_us() - t_enc_start;
if (transcribe::debug::enabled() && transcribe::debug::dump_all_blocks_requested()) {
if (eb.dumps.pre_encode_out != nullptr) {
transcribe::debug::dump_tensor("enc.pre_encode.out", eb.dumps.pre_encode_out, "encoder.pre_encode");
}
for (size_t i = 0; i < eb.dumps.all_block_outs.size(); ++i) {
ggml_tensor * t = eb.dumps.all_block_outs[i];
if (t == nullptr) {
continue;
}
char name[64];
std::snprintf(name, sizeof(name), "enc.block.%zu.out", i);
transcribe::debug::dump_tensor(name, t, "encoder.block.bisect");
}
}
const size_t utt_elems = static_cast<size_t>(d_enc) * static_cast<size_t>(T_enc);
pc->enc_host.resize(utt_elems * static_cast<size_t>(n));
ggml_backend_tensor_get(eb.out, pc->enc_host.data(), 0, pc->enc_host.size() * sizeof(float));
const bool has_prompt = pm->hparams.has_prompt && eb.dumps.final_out != nullptr;
std::vector<float> unprompted_host;
if (has_prompt && transcribe::debug::enabled()) {
unprompted_host.resize(pc->enc_host.size());
ggml_backend_tensor_get(eb.dumps.final_out, unprompted_host.data(), 0, unprompted_host.size() * sizeof(float));
for (int b = 0; b < n; ++b) {
const long long shape[2] = { real_tenc[b], d_enc };
char namebuf[64];
std::snprintf(namebuf, sizeof(namebuf), "dec.enc_out.b%d", b);
transcribe::debug::dump_host_f32(namebuf, unprompted_host.data() + static_cast<size_t>(b) * utt_elems,
static_cast<long long>(real_tenc[b]) * static_cast<long long>(d_enc),
shape, 2, "decoder.enc_out");
}
}
const char * enc_dump_name = has_prompt ? "dec.enc_out_prompted" : nullptr;
return transcribe::decode_batch_slices(
pc, n, pc->enc_host.data(), utt_elems, pc->t_encode_us, total_mel_us, [&](int b, const float * enc_b) {
return decode_and_populate(pc, pm, params, enc_b, real_tenc[b], d_enc, b, enc_dump_name);
});
}
transcribe_status run_batch(transcribe_session * session,
const float * const * pcm,
const int * n_samples,
int n,
const transcribe_run_params * params) {
if (session == nullptr || pcm == nullptr || n_samples == nullptr || n <= 0) {
return TRANSCRIBE_ERR_INVALID_ARG;
}
auto * pc = static_cast<ParakeetSession *>(session);
auto * pm = static_cast<ParakeetModel *>(pc->model);
if (pm == nullptr || pm->plan.scheduler_list.empty() || !pm->mel.has_value()) {
return TRANSCRIBE_ERR_INVALID_ARG;
}
transcribe::debug::init();
std::vector<std::vector<float>> mels(static_cast<size_t>(n));
std::vector<int> nf(static_cast<size_t>(n), 0);
std::vector<int> n_mels_per(static_cast<size_t>(n), 0);
const int64_t t_mel_start = ggml_time_us();
const bool all_ok = transcribe::parallel_for_all(n, pc->n_threads, [&](int i) -> bool {
if (pcm[i] == nullptr || n_samples[i] <= 0) {
return false;
}
int this_mels = 0, this_frames = 0;
const transcribe_status st =
pm->mel->compute(pcm[i], static_cast<size_t>(n_samples[i]), mels[i], this_mels, this_frames);
if (st != TRANSCRIBE_OK || this_frames <= 0) {
return false;
}
nf[i] = this_frames;
n_mels_per[i] = this_mels;
return true;
});
const int64_t total_mel_us = ggml_time_us() - t_mel_start;
if (all_ok) {
int T_max = 0, n_mels = 0;
for (int i = 0; i < n; ++i) {
T_max = std::max(T_max, nf[i]);
n_mels = std::max(n_mels, n_mels_per[i]);
}
return run_batch_encode(pc, pm, mels, nf, n_mels, T_max, total_mel_us, params);
}
for (int i = 0; i < n; ++i) {
if (pc->poll_abort()) {
return TRANSCRIBE_ERR_ABORTED;
}
if (pcm[i] == nullptr || n_samples[i] <= 0) {
transcribe_session::ResultSet rs;
rs.status = TRANSCRIBE_ERR_INVALID_ARG;
pc->batch_results.push_back(std::move(rs));
continue;
}
pc->clear_result();
const transcribe_status st = run_one_shot_inner(pc, pm, pcm[i], n_samples[i], params);
pc->batch_results.push_back(pc->capture_result(st));
}
return TRANSCRIBE_OK;
}
constexpr int64_t k_sample_rate_hz = 16000;
static int64_t samples_to_us(int64_t n_samples) {
return (n_samples * 1000000) / k_sample_rate_hz;
}
static int64_t us_to_ms(int64_t us) {
return us / 1000;
}
void fill_streaming_pos_emb(ParakeetSession * pc, ggml_tensor * pos_emb_in, int d_model, int zero_index) {
const int pos_len = static_cast<int>(pos_emb_in->ne[1]);
pc->pos_buf.assign(static_cast<size_t>(pos_len) * d_model, 0.0f);
pc->pos_div_term.resize(static_cast<size_t>(d_model / 2));
const float ln_10000 = std::log(10000.0f);
for (int k = 0; k < d_model / 2; ++k) {
pc->pos_div_term[static_cast<size_t>(k)] =
std::exp(static_cast<float>(2 * k) * (-ln_10000 / static_cast<float>(d_model)));
}
for (int i = 0; i < pos_len; ++i) {
const float pos = static_cast<float>(zero_index - i);
float * row = pc->pos_buf.data() + static_cast<size_t>(i) * d_model;
for (int k = 0; k < d_model / 2; ++k) {
const float div = pc->pos_div_term[static_cast<size_t>(k)];
row[2 * k] = std::sin(pos * div);
row[2 * k + 1] = std::cos(pos * div);
}
}
ggml_backend_tensor_set(pos_emb_in, pc->pos_buf.data(), 0, pc->pos_buf.size() * sizeof(float));
}
void fill_streaming_chunked_mask(ggml_tensor * mask_in,
int T_virtual,
int T_cache,
int channel_len,
int att_context_left,
int att_context_right) {
const int n_q_rows = static_cast<int>(mask_in->ne[1]);
const int q_first = T_virtual - n_q_rows;
const int chunk_size = att_context_right + 1;
const int left_chunks = (chunk_size > 0) ? (att_context_left / chunk_size) : 0;
const int invalid_cache_threshold = T_cache - channel_len;
std::vector<float> mask_buf(static_cast<size_t>(n_q_rows) * static_cast<size_t>(T_virtual));
for (int q = q_first; q < T_virtual; ++q) {
const int q_chunk = (chunk_size > 0) ? (q / chunk_size) : 0;
const int k_min_chunk = (q_chunk - left_chunks > 0) ? (q_chunk - left_chunks) : 0;
const int k_min = k_min_chunk * chunk_size;
const int k_max = (q_chunk + 1) * chunk_size; float * row = mask_buf.data() + static_cast<size_t>(q - q_first) * T_virtual;
for (int k = 0; k < T_virtual; ++k) {
const bool in_band = (k >= k_min && k < k_max);
const bool cache_unfilled = (k < invalid_cache_threshold);
row[k] = (in_band && !cache_unfilled) ? 0.0f : -std::numeric_limits<float>::infinity();
}
}
ggml_backend_tensor_set(mask_in, mask_buf.data(), 0, mask_buf.size() * sizeof(float));
}
transcribe_status ensure_pos_proj_cache(ParakeetSession * pc, ParakeetModel * pm, int pos_len) {
const auto & hp = pm->hparams;
const int n_layers = static_cast<int>(pm->weights.blocks.size());
const int d_model = hp.enc_d_model;
const int n_head = hp.enc_n_heads;
const int head_dim = d_model / n_head;
if (pc->pos_buf.size() < static_cast<size_t>(pos_len) * static_cast<size_t>(d_model)) {
log_msg(TRANSCRIBE_LOG_LEVEL_ERROR,
"parakeet stream: pos_proj cache fill without pos_buf "
"(%zu < %d*%d)",
pc->pos_buf.size(), pos_len, d_model);
return TRANSCRIBE_ERR_BACKEND;
}
auto & sc = pc->stream_caches;
if (sc.pos_proj_buf != nullptr) {
safe_buffer_free(sc.pos_proj_buf);
sc.pos_proj_buf = nullptr;
}
if (sc.pos_proj_ctx != nullptr) {
ggml_free(sc.pos_proj_ctx);
sc.pos_proj_ctx = nullptr;
}
sc.pos_proj.assign(static_cast<size_t>(n_layers), nullptr);
sc.pos_proj_len = -1;
ggml_init_params pip{};
pip.mem_size = (static_cast<size_t>(n_layers) + 2) * ggml_tensor_overhead();
pip.mem_buffer = nullptr;
pip.no_alloc = true;
sc.pos_proj_ctx = ggml_init(pip);
if (sc.pos_proj_ctx == nullptr) {
return TRANSCRIBE_ERR_OOM;
}
for (int i = 0; i < n_layers; ++i) {
ggml_tensor * t = ggml_new_tensor_3d(sc.pos_proj_ctx, GGML_TYPE_F32, head_dim, pos_len, n_head);
if (t == nullptr) {
return TRANSCRIBE_ERR_OOM;
}
char nm[64];
std::snprintf(nm, sizeof(nm), "stream.pos_proj.%d", i);
ggml_set_name(t, nm);
sc.pos_proj[static_cast<size_t>(i)] = t;
}
sc.pos_proj_buf = ggml_backend_alloc_ctx_tensors(sc.pos_proj_ctx, pm->plan.primary);
if (sc.pos_proj_buf == nullptr) {
return TRANSCRIBE_ERR_OOM;
}
ggml_init_params gip{};
gip.mem_size = 4 * 1024 * 1024;
gip.mem_buffer = nullptr;
gip.no_alloc = true;
ggml_context * gctx = ggml_init(gip);
if (gctx == nullptr) {
return TRANSCRIBE_ERR_OOM;
}
ggml_cgraph * graph = ggml_new_graph_custom(gctx, 512, false);
ggml_tensor * pos_in = ggml_new_tensor_2d(gctx, GGML_TYPE_F32, d_model, pos_len);
ggml_set_name(pos_in, "pos_proj.pos_emb.in");
ggml_set_input(pos_in);
for (int i = 0; i < n_layers; ++i) {
ggml_tensor * p = ggml_mul_mat(gctx, pm->weights.blocks[static_cast<size_t>(i)].attn_pos_w, pos_in);
p = ggml_reshape_4d(gctx, p, head_dim, n_head, pos_len, 1);
p = ggml_cont(gctx, ggml_permute(gctx, p, 0, 2, 1, 3));
ggml_tensor * cpy = ggml_cpy(gctx, p, sc.pos_proj[static_cast<size_t>(i)]);
ggml_build_forward_expand(graph, cpy);
}
transcribe_status st = TRANSCRIBE_OK;
ggml_backend_sched_reset(pc->sched);
if (!ggml_backend_sched_alloc_graph(pc->sched, graph)) {
st = TRANSCRIBE_ERR_BACKEND;
} else {
ggml_backend_tensor_set(pos_in, pc->pos_buf.data(), 0, static_cast<size_t>(pos_len) * d_model * sizeof(float));
if (ggml_backend_sched_graph_compute(pc->sched, graph) != GGML_STATUS_SUCCESS) {
st = TRANSCRIBE_ERR_BACKEND;
}
}
ggml_free(gctx);
if (st == TRANSCRIBE_OK) {
sc.pos_proj_len = pos_len;
}
return st;
}
}
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) {
assert(out_buf != nullptr);
assert(T >= 1);
assert(chunk_size_frames >= 1);
assert(left_context_frames >= 0);
assert(right_context_frames >= 0);
assert(pad_length >= 0);
const int L = left_context_frames;
const int C = chunk_size_frames;
const int R = right_context_frames;
const int P = pad_length > T ? T : pad_length;
for (int q = 0; q < T; ++q) {
const int c_q = q / C;
const int window_start_unclamped = c_q * C - L;
const int window_end_unclamped = c_q * C + C - 1 + R;
const int window_start = window_start_unclamped > 0 ? window_start_unclamped : 0;
const int window_end = window_end_unclamped < (T - 1) ? window_end_unclamped : (T - 1);
const bool q_padded = q >= P;
float * row = out_buf + static_cast<size_t>(q) * static_cast<size_t>(T);
for (int k = 0; k < T; ++k) {
const bool k_padded = k >= P;
const bool allowed = (k >= window_start && k <= window_end) && !q_padded && !k_padded;
row[k] = allowed ? 0.0f : -std::numeric_limits<float>::infinity();
}
}
}
namespace {
transcribe_status emit_streaming_chunk(ParakeetSession * pc,
ParakeetModel * pm,
const float * mel_chunk_data,
int n_mel_chunk_frames,
int drop_extra_pre_encoded,
int mel_frames_advance) {
const auto & hp = pm->hparams;
const int n_layers = static_cast<int>(pm->weights.blocks.size());
ggml_type resolved_kv = GGML_TYPE_COUNT;
if (pc->kv_type == TRANSCRIBE_KV_TYPE_F32) {
resolved_kv = GGML_TYPE_F32;
}
if (pc->kv_type == TRANSCRIBE_KV_TYPE_F16) {
resolved_kv = GGML_TYPE_F16;
}
if (pc->compute_ctx != nullptr) {
ggml_free(pc->compute_ctx);
pc->compute_ctx = nullptr;
}
ggml_init_params ip{};
ip.mem_size = 16 * 1024 * 1024;
ip.mem_buffer = nullptr;
ip.no_alloc = true;
pc->compute_ctx = ggml_init(ip);
if (pc->compute_ctx == nullptr) {
log_msg(TRANSCRIBE_LOG_LEVEL_ERROR, "parakeet stream: ggml_init compute_ctx failed");
return TRANSCRIBE_ERR_OOM;
}
const int step_num = pc->stream_caches.chunk_step;
const bool dump_on = transcribe::debug::enabled();
auto sel_layers = [&]() {
std::vector<int> v;
if (n_layers <= 0) {
return v;
}
std::set<int> s = { 0, n_layers / 2, n_layers - 1 };
for (int i : s) {
if (i >= 0 && i < n_layers) {
v.push_back(i);
}
}
std::sort(v.begin(), v.end());
return v;
}();
if (dump_on) {
char namebuf[128];
std::snprintf(namebuf, sizeof(namebuf), "stream.chunk.%d.mel_in", step_num);
const long long mel_shape[2] = { hp.fe_num_mels, n_mel_chunk_frames };
transcribe::debug::dump_host_f32(namebuf, mel_chunk_data,
static_cast<long long>(hp.fe_num_mels) * n_mel_chunk_frames, mel_shape, 2,
"streaming.mel_in");
for (int L : sel_layers) {
std::snprintf(namebuf, sizeof(namebuf), "stream.chunk.%d.cache_lc_in_%d", step_num, L);
transcribe::debug::dump_tensor(namebuf, pc->stream_caches.last_channel[L], "streaming.cache_in");
std::snprintf(namebuf, sizeof(namebuf), "stream.chunk.%d.cache_lt_in_%d", step_num, L);
transcribe::debug::dump_tensor(namebuf, pc->stream_caches.last_time[L], "streaming.cache_in");
}
}
StreamingEncoderCacheIO cache_io;
cache_io.channel_in = pc->stream_caches.last_channel;
cache_io.time_in = pc->stream_caches.last_time;
cache_io.k_in = pc->stream_caches.last_k;
cache_io.v_in = pc->stream_caches.last_v;
cache_io.pos_proj = pc->stream_caches.pos_proj;
cache_io.pos_proj_len = pc->stream_caches.pos_proj_len;
EncoderBuild eb = build_encoder_graph_streaming(pc->compute_ctx, pm->weights, hp, n_mel_chunk_frames,
drop_extra_pre_encoded, cache_io, resolved_kv, pm->backend.c_str());
if (eb.out == nullptr || eb.graph == nullptr) {
return TRANSCRIBE_ERR_GGUF;
}
if (pc->sched == nullptr) {
pc->sched = ggml_backend_sched_new(pm->plan.scheduler_list.data(), nullptr,
static_cast<int>(pm->plan.scheduler_list.size()),
8192, false, true);
if (pc->sched == nullptr) {
log_msg(TRANSCRIBE_LOG_LEVEL_ERROR, "parakeet stream: sched_new failed");
return TRANSCRIBE_ERR_BACKEND;
}
}
ggml_backend_sched_reset(pc->sched);
if (!ggml_backend_sched_alloc_graph(pc->sched, eb.graph)) {
log_msg(TRANSCRIBE_LOG_LEVEL_ERROR, "parakeet stream: alloc_graph failed");
return TRANSCRIBE_ERR_BACKEND;
}
ggml_backend_tensor_set(
eb.mel_in, mel_chunk_data, 0,
static_cast<size_t>(n_mel_chunk_frames) * static_cast<size_t>(hp.fe_num_mels) * sizeof(float));
const int T_virtual = static_cast<int>(eb.chunked_mask_in->ne[0]);
const int T_cache = hp.enc_att_context_left;
const int T_q_new = T_virtual - T_cache;
if (eb.pos_emb_in != nullptr) {
fill_streaming_pos_emb(pc, eb.pos_emb_in, hp.enc_d_model,
T_virtual - 1);
}
fill_streaming_chunked_mask(eb.chunked_mask_in, T_virtual, T_cache, pc->stream_caches.channel_len,
pc->stream_caches.att_context_left, pc->stream_caches.att_context_right);
if (eb.prompt_one_hot_in != nullptr) {
const int P = static_cast<int>(eb.prompt_one_hot_in->ne[0]);
const int T_oh = static_cast<int>(eb.prompt_one_hot_in->ne[1]);
const char * lang_hint = pc->stream_run_params.language;
const int32_t pid = resolve_prompt_id(pm->hparams, lang_hint);
if (pid < 0) {
log_msg(TRANSCRIBE_LOG_LEVEL_ERROR,
"parakeet stream: language %s%s%s not in prompt "
"dictionary",
lang_hint ? "\"" : "", lang_hint ? lang_hint : "<null>", lang_hint ? "\"" : "");
return TRANSCRIBE_ERR_UNSUPPORTED_LANGUAGE;
}
std::vector<float> one_hot_buf;
if (!fill_prompt_one_hot(one_hot_buf, P, T_oh, 1, { pid })) {
log_msg(TRANSCRIBE_LOG_LEVEL_ERROR,
"parakeet stream: prompt_id %d out of range "
"[0, %d)",
pid, P);
return TRANSCRIBE_ERR_GGUF;
}
ggml_backend_tensor_set(eb.prompt_one_hot_in, one_hot_buf.data(), 0, one_hot_buf.size() * sizeof(float));
}
transcribe::configure_sched_n_threads(pc->sched, pc->n_threads);
if (const ggml_status gs = ggml_backend_sched_graph_compute(pc->sched, eb.graph); gs != GGML_STATUS_SUCCESS) {
log_msg(TRANSCRIBE_LOG_LEVEL_ERROR, "parakeet stream: graph_compute failed (%d)", static_cast<int>(gs));
return TRANSCRIBE_ERR_GGUF;
}
const int d_enc = static_cast<int>(eb.out->ne[0]);
pc->enc_host.resize(static_cast<size_t>(d_enc) * static_cast<size_t>(T_q_new));
ggml_backend_tensor_get(eb.out, pc->enc_host.data(), 0, pc->enc_host.size() * sizeof(float));
if (dump_on) {
char namebuf[128];
std::snprintf(namebuf, sizeof(namebuf), "stream.chunk.%d.enc_out", step_num);
if (T_q_new == 1) {
const long long enc_shape[1] = { d_enc };
transcribe::debug::dump_host_f32(namebuf, pc->enc_host.data(), d_enc, enc_shape, 1, "streaming.enc_out");
} else {
const long long enc_shape[2] = { T_q_new, d_enc };
transcribe::debug::dump_host_f32(namebuf, pc->enc_host.data(), static_cast<long long>(T_q_new) * d_enc,
enc_shape, 2, "streaming.enc_out");
}
for (int L : sel_layers) {
std::snprintf(namebuf, sizeof(namebuf), "stream.chunk.%d.cache_lc_out_%d", step_num, L);
transcribe::debug::dump_tensor(namebuf, cache_io.channel_out[L], "streaming.cache_out");
std::snprintf(namebuf, sizeof(namebuf), "stream.chunk.%d.cache_lt_out_%d", step_num, L);
transcribe::debug::dump_tensor(namebuf, cache_io.time_out[L], "streaming.cache_out");
}
}
const bool kv_mode = !cache_io.k_out.empty();
for (int i = 0; i < n_layers; ++i) {
ggml_tensor * ch = cache_io.channel_out[i];
ggml_tensor * tm = cache_io.time_out[i];
ggml_tensor * ko = kv_mode ? cache_io.k_out[i] : nullptr;
ggml_tensor * vo = kv_mode ? cache_io.v_out[i] : nullptr;
const bool attn_ok = kv_mode ?
(ko != nullptr && ko->buffer != nullptr && vo != nullptr && vo->buffer != nullptr) :
(ch != nullptr && ch->buffer != nullptr);
if (!attn_ok || tm == nullptr || tm->buffer == nullptr) {
log_msg(TRANSCRIBE_LOG_LEVEL_ERROR,
"parakeet stream: cache_out unallocated at layer %d "
"(att_context_right=%d) — builder bug",
i, pc->stream_caches.att_context_right);
return TRANSCRIBE_ERR_BACKEND;
}
if (kv_mode) {
ggml_backend_tensor_copy(ko, pc->stream_caches.last_k[i]);
ggml_backend_tensor_copy(vo, pc->stream_caches.last_v[i]);
} else {
ggml_backend_tensor_copy(ch, pc->stream_caches.last_channel[i]);
}
ggml_backend_tensor_copy(tm, pc->stream_caches.last_time[i]);
}
pc->stream_caches.channel_len = std::min(T_cache, pc->stream_caches.channel_len + T_q_new);
if (eb.pos_emb_in != nullptr) {
const int pos_len_cur = static_cast<int>(eb.pos_emb_in->ne[1]);
if (pos_len_cur != pc->stream_caches.pos_proj_len) {
if (const transcribe_status st = ensure_pos_proj_cache(pc, pm, pos_len_cur); st != TRANSCRIBE_OK) {
log_msg(TRANSCRIBE_LOG_LEVEL_ERROR,
"parakeet stream: pos_proj cache fill failed (%s) — "
"continuing without memoization",
transcribe_status_string(st));
}
}
}
if (dump_on) {
char namebuf[128];
std::snprintf(namebuf, sizeof(namebuf), "stream.chunk.%d.channel_len", step_num);
const float channel_len_f = static_cast<float>(pc->stream_caches.channel_len);
const long long len_shape[1] = { 1 };
transcribe::debug::dump_host_f32(namebuf, &channel_len_f, 1, len_shape, 1, "streaming.channel_len");
}
pc->stream_caches.chunk_step += 1;
if (const transcribe_status st = decode_rnnt_greedy_streaming(
pm->host_decoder, pc->enc_host.data(), T_q_new, d_enc, pc->stream_dec_state.lstm_state,
pc->stream_dec_state.prev_token_id, static_cast<int>(pc->stream_dec_state.frame_offset), pc->n_threads,
pc->raw_tokens);
st != TRANSCRIBE_OK) {
return st;
}
pc->stream_dec_state.frame_offset += T_q_new;
pc->stream_caches.mel_frames_consumed += mel_frames_advance;
return TRANSCRIBE_OK;
}
void rebuild_streaming_result_text(ParakeetSession * pc, const ParakeetModel * pm) {
pc->tokens.clear();
pc->words.clear();
pc->segments.clear();
pc->full_text.clear();
if (pc->raw_tokens.empty()) {
pc->has_result = false;
pc->result_kind = TRANSCRIBE_TIMESTAMPS_NONE;
return;
}
const double frame_to_ms = parakeet_ms_per_enc_frame(pm->hparams);
const transcribe::Tokenizer & tok = pm->tok;
const bool strip_tags = !pc->stream_run_params.keep_special_tags;
pc->tokens.reserve(pc->raw_tokens.size());
std::vector<int32_t> all_ids;
all_ids.reserve(pc->raw_tokens.size());
for (const auto & tk : pc->raw_tokens) {
if (strip_tags && is_strippable_special(tok, tk.id)) {
continue;
}
transcribe_session::TokenEntry te;
te.id = tk.id;
te.p = tk.p;
te.t0_ms = static_cast<int64_t>(std::llround(frame_to_ms * static_cast<double>(tk.step_at_emit)));
te.t1_ms =
static_cast<int64_t>(std::llround(frame_to_ms * static_cast<double>(tk.step_at_emit + tk.duration_frames)));
te.seg_index = 0;
te.word_index = -1;
te.text = tok.decode(&tk.id, 1);
pc->tokens.push_back(std::move(te));
all_ids.push_back(tk.id);
}
pc->full_text = tok.decode(all_ids.data(), static_cast<int>(all_ids.size()));
normalize_transcript_whitespace(pc->full_text);
pc->has_result = true;
pc->result_kind = TRANSCRIBE_TIMESTAMPS_TOKEN;
}
static void buf_ctx_add_frames(ParakeetSession * pc, int64_t num_new, bool is_last) {
pc->buf_ctx_left += pc->buf_ctx_chunk;
pc->buf_ctx_chunk = 0;
pc->buf_ctx_right += num_new;
if (is_last) {
pc->buf_ctx_chunk = pc->buf_ctx_right;
pc->buf_ctx_right = 0;
} else {
pc->buf_ctx_chunk = pc->buf_samples_chunk;
pc->buf_ctx_right -= static_cast<int64_t>(pc->buf_samples_chunk);
}
const int64_t total_now = pc->buf_ctx_left + pc->buf_ctx_chunk + pc->buf_ctx_right;
const int64_t expected = static_cast<int64_t>(pc->buf_samples_left) + static_cast<int64_t>(pc->buf_samples_chunk) +
static_cast<int64_t>(pc->buf_samples_right);
const int64_t extra = std::max<int64_t>(total_now - expected, 0);
pc->buf_ctx_left -= extra;
}
transcribe_status emit_buffered_chunk(ParakeetSession * pc,
ParakeetModel * pm,
int64_t num_new_samples,
bool is_last_chunk) {
if (pc->poll_abort()) {
return TRANSCRIBE_ERR_ABORTED;
}
const auto & hp = pm->hparams;
if (!pm->mel.has_value()) {
log_msg(TRANSCRIBE_LOG_LEVEL_ERROR, "parakeet buffered: model has no MelFrontend");
return TRANSCRIBE_ERR_INVALID_ARG;
}
const int samples_per_frame = hp.enc_subsampling_factor * hp.fe_hop_length;
if (samples_per_frame <= 0) {
return TRANSCRIBE_ERR_INVALID_ARG;
}
buf_ctx_add_frames(pc, num_new_samples, is_last_chunk);
const int64_t end_abs = pc->buf_next_audio_read + num_new_samples;
const int64_t total_now = pc->buf_ctx_left + pc->buf_ctx_chunk + pc->buf_ctx_right;
const int effective_T = static_cast<int>(total_now / samples_per_frame);
const int64_t start_abs = end_abs - total_now;
std::vector<float> window_pcm(static_cast<size_t>(total_now), 0.0f);
const int64_t buf_size = static_cast<int64_t>(pc->stream_pcm_buffer.size());
for (int64_t i = 0; i < total_now; ++i) {
const int64_t src = start_abs + i;
if (src >= 0 && src < buf_size) {
window_pcm[static_cast<size_t>(i)] = pc->stream_pcm_buffer[static_cast<size_t>(src)];
}
}
const int chunk_step = pc->buf_chunk_step;
char chunk_prefix[64];
std::snprintf(chunk_prefix, sizeof(chunk_prefix), "stream.chunk.%d.", chunk_step);
transcribe::debug::push_name_prefix(chunk_prefix);
struct PrefixPopGuard {
~PrefixPopGuard() { transcribe::debug::pop_name_prefix(); }
} _prefix_pop_guard;
if (transcribe::debug::enabled()) {
const long long shape[1] = { static_cast<long long>(window_pcm.size()) };
transcribe::debug::dump_host_f32("audio_in", window_pcm.data(), static_cast<long long>(window_pcm.size()),
shape, 1, "buffered_streaming.audio_in");
}
const int64_t t_mel_start = ggml_time_us();
int mel_n_mels = 0;
int mel_n_frames = 0;
if (const transcribe_status mst =
pm->mel->compute(window_pcm.data(), window_pcm.size(), pc->mel_buf, mel_n_mels, mel_n_frames);
mst != TRANSCRIBE_OK) {
log_msg(TRANSCRIBE_LOG_LEVEL_ERROR, "parakeet buffered: MelFrontend::compute failed (%s)",
transcribe_status_string(mst));
return mst;
}
pc->t_mel_us += ggml_time_us() - t_mel_start;
if (pc->compute_ctx != nullptr) {
ggml_free(pc->compute_ctx);
pc->compute_ctx = nullptr;
}
pc->encoder_out = nullptr;
{
ggml_init_params init_params{};
init_params.mem_size = 4 * 1024 * 1024;
init_params.mem_buffer = nullptr;
init_params.no_alloc = true;
pc->compute_ctx = ggml_init(init_params);
if (pc->compute_ctx == nullptr) {
log_msg(TRANSCRIBE_LOG_LEVEL_ERROR, "parakeet buffered: ggml_init for compute_ctx failed");
return TRANSCRIBE_ERR_GGUF;
}
}
ggml_type resolved_kv = GGML_TYPE_COUNT;
if (pc->kv_type == TRANSCRIBE_KV_TYPE_F32) {
resolved_kv = GGML_TYPE_F32;
}
if (pc->kv_type == TRANSCRIBE_KV_TYPE_F16) {
resolved_kv = GGML_TYPE_F16;
}
BufferedStreamMaskOverride buf_mask{};
buf_mask.left_frames = pc->buf_left_frames;
buf_mask.chunk_frames = pc->buf_chunk_frames;
buf_mask.right_frames = pc->buf_right_frames;
buf_mask.valid_frames = effective_T;
EncoderBuild eb = build_encoder_graph(pc->compute_ctx, pm->weights, pm->hparams, mel_n_frames, resolved_kv,
pm->backend.c_str(), &buf_mask);
if (eb.mel_in == nullptr || eb.out == nullptr || eb.graph == nullptr) {
return TRANSCRIBE_ERR_GGUF;
}
if (pc->sched == nullptr) {
pc->sched = ggml_backend_sched_new(pm->plan.scheduler_list.data(), nullptr,
static_cast<int>(pm->plan.scheduler_list.size()),
8192, false, true);
if (pc->sched == nullptr) {
return TRANSCRIBE_ERR_GGUF;
}
}
ggml_backend_sched_reset(pc->sched);
if (!ggml_backend_sched_alloc_graph(pc->sched, eb.graph)) {
return TRANSCRIBE_ERR_GGUF;
}
ggml_backend_tensor_set(eb.mel_in, pc->mel_buf.data(), 0, pc->mel_buf.size() * sizeof(float));
if (eb.pos_emb_in != nullptr) {
const int d_model = hp.enc_d_model;
const int pos_len = static_cast<int>(eb.pos_emb_in->ne[1]);
const int zero_index = (pos_len - 1) / 2;
pc->pos_buf.assign(static_cast<size_t>(pos_len) * d_model, 0.0f);
pc->pos_div_term.resize(static_cast<size_t>(d_model / 2));
const float ln_10000 = std::log(10000.0f);
for (int k = 0; k < d_model / 2; ++k) {
pc->pos_div_term[static_cast<size_t>(k)] =
std::exp(static_cast<float>(2 * k) * (-ln_10000 / static_cast<float>(d_model)));
}
for (int i = 0; i < pos_len; ++i) {
const float pos = static_cast<float>(zero_index - i);
float * row = pc->pos_buf.data() + static_cast<size_t>(i) * d_model;
for (int k = 0; k < d_model / 2; ++k) {
const float div = pc->pos_div_term[static_cast<size_t>(k)];
row[2 * k] = std::sin(pos * div);
row[2 * k + 1] = std::cos(pos * div);
}
}
ggml_backend_tensor_set(eb.pos_emb_in, pc->pos_buf.data(), 0, pc->pos_buf.size() * sizeof(float));
}
if (eb.conv_pad_mask_in != nullptr) {
const int T_enc = static_cast<int>(eb.conv_pad_mask_in->ne[0]);
const int P = std::max(0, std::min(effective_T, T_enc));
std::vector<float> mask_buf(static_cast<size_t>(T_enc), 1.0f);
for (int t = P; t < T_enc; ++t) {
mask_buf[static_cast<size_t>(t)] = 0.0f;
}
ggml_backend_tensor_set(eb.conv_pad_mask_in, mask_buf.data(), 0, mask_buf.size() * sizeof(float));
transcribe::debug::dump_tensor("enc.conv.pad_mask", eb.conv_pad_mask_in, "encoder.conv.pad_mask");
}
if (eb.chunked_mask_in != nullptr) {
const int T_enc = static_cast<int>(eb.chunked_mask_in->ne[0]);
std::vector<float> mask_buf(static_cast<size_t>(T_enc) * static_cast<size_t>(T_enc));
compute_chunked_limited_with_rc_mask(mask_buf.data(), T_enc, pc->buf_left_frames, pc->buf_chunk_frames,
pc->buf_right_frames, effective_T);
ggml_backend_tensor_set(eb.chunked_mask_in, mask_buf.data(), 0, mask_buf.size() * sizeof(float));
}
transcribe::configure_sched_n_threads(pc->sched, pc->n_threads);
const int64_t t_enc_start = ggml_time_us();
if (const ggml_status gs = ggml_backend_sched_graph_compute(pc->sched, eb.graph); gs != GGML_STATUS_SUCCESS) {
log_msg(TRANSCRIBE_LOG_LEVEL_ERROR, "parakeet buffered: sched_graph_compute failed (%d)", static_cast<int>(gs));
return TRANSCRIBE_ERR_GGUF;
}
pc->t_encode_us += ggml_time_us() - t_enc_start;
pc->encoder_out = eb.out;
if (transcribe::debug::enabled()) {
transcribe::debug::dump_tensor("enc.mel.in", eb.mel_in, "encoder.mel");
}
if (transcribe::debug::enabled()) {
auto try_dump = [](const char * name, ggml_tensor * t, const char * stage) {
if (t != nullptr) {
transcribe::debug::dump_tensor(name, t, stage);
}
};
try_dump("enc.pre_encode.out", eb.dumps.pre_encode_out, "encoder.pre_encode");
try_dump("enc.block.0.ff1", eb.dumps.block0_after_ff1, "encoder.block0.ff1");
try_dump("enc.block.0.attn", eb.dumps.block0_after_attn, "encoder.block0.attn");
try_dump("enc.block.0.conv", eb.dumps.block0_after_conv, "encoder.block0.conv");
try_dump("enc.block.0.ff2", eb.dumps.block0_after_ff2, "encoder.block0.ff2");
try_dump("enc.block.0.out", eb.dumps.block0_out, "encoder.block0.out");
if (eb.dumps.mid_block_out != nullptr && eb.dumps.mid_block_idx >= 0) {
char name[64];
std::snprintf(name, sizeof(name), "enc.block.%d.out", eb.dumps.mid_block_idx);
transcribe::debug::dump_tensor(name, eb.dumps.mid_block_out, "encoder.block.mid.out");
}
if (eb.dumps.last_block_out != nullptr && eb.dumps.last_block_idx >= 0) {
char name[64];
std::snprintf(name, sizeof(name), "enc.block.%d.out", eb.dumps.last_block_idx);
transcribe::debug::dump_tensor(name, eb.dumps.last_block_out, "encoder.block.last.out");
}
if (transcribe::debug::dump_all_blocks_requested()) {
for (size_t i = 0; i < eb.dumps.all_block_outs.size(); ++i) {
ggml_tensor * t = eb.dumps.all_block_outs[i];
if (t == nullptr) {
continue;
}
char name[64];
std::snprintf(name, sizeof(name), "enc.block.%zu.out", i);
transcribe::debug::dump_tensor(name, t, "encoder.block.bisect");
}
}
}
const int d_enc = static_cast<int>(eb.out->ne[0]);
const int T_enc_full = static_cast<int>(eb.out->ne[1]);
if (T_enc_full <= 0) {
log_msg(TRANSCRIBE_LOG_LEVEL_ERROR, "parakeet buffered: encoder produced %d frames", T_enc_full);
return TRANSCRIBE_ERR_GGUF;
}
pc->enc_host.resize(static_cast<size_t>(d_enc) * static_cast<size_t>(T_enc_full));
ggml_backend_tensor_get(eb.out, pc->enc_host.data(), 0, pc->enc_host.size() * sizeof(float));
transcribe::debug::dump_tensor("enc.final", eb.out, "encoder.final");
const int ctx_left_frames = static_cast<int>(pc->buf_ctx_left / samples_per_frame);
const int ctx_chunk_frames = static_cast<int>(pc->buf_ctx_chunk / samples_per_frame);
auto advance_cursor = [&]() {
pc->buf_next_audio_read += num_new_samples;
pc->buf_chunk_step += 1;
pc->buf_initialized = true;
};
if (T_enc_full <= ctx_left_frames) {
advance_cursor();
return TRANSCRIBE_OK;
}
const int T_chunk_avail = T_enc_full - ctx_left_frames;
const int T_to_decode = is_last_chunk ? T_chunk_avail : std::min(ctx_chunk_frames, T_chunk_avail);
if (T_to_decode <= 0) {
advance_cursor();
return TRANSCRIBE_OK;
}
const float * enc_chunk = pc->enc_host.data() + static_cast<size_t>(ctx_left_frames) * static_cast<size_t>(d_enc);
if (transcribe::debug::enabled()) {
const long long shape[2] = {
static_cast<long long>(T_chunk_avail),
static_cast<long long>(d_enc),
};
transcribe::debug::dump_host_f32("enc_out", enc_chunk,
static_cast<long long>(T_chunk_avail) * static_cast<long long>(d_enc), shape,
2, "buffered_streaming.enc_out");
}
const int64_t t_dec_start = ggml_time_us();
if (const transcribe_status st = decode_rnnt_greedy_streaming(
pm->host_decoder, enc_chunk, T_to_decode, d_enc, pc->stream_dec_state.lstm_state,
pc->stream_dec_state.prev_token_id, static_cast<int>(pc->stream_dec_state.frame_offset), pc->n_threads,
pc->raw_tokens);
st != TRANSCRIBE_OK) {
return st;
}
pc->t_decode_us += ggml_time_us() - t_dec_start;
pc->stream_dec_state.frame_offset += T_to_decode;
advance_cursor();
return TRANSCRIBE_OK;
}
transcribe_status resolve_buffered_stream_geom(const ParakeetModel * pm,
const transcribe_stream_params * stream_params,
int * out_L_frames,
int * out_C_frames,
int * out_R_frames) {
const int frame_ms = (pm->hparams.enc_subsampling_factor * pm->hparams.fe_hop_length * 1000) /
std::max(pm->hparams.fe_sample_rate, 1);
auto max_in = [](const std::vector<int32_t> & v) -> int32_t {
int32_t best = 0;
for (auto x : v) {
if (x > best) {
best = x;
}
}
return best;
};
const int default_L = static_cast<int>(max_in(pm->hparams.enc_att_chunk_left_choices));
const int default_C = static_cast<int>(max_in(pm->hparams.enc_att_chunk_chunk_choices));
const int default_R = static_cast<int>(max_in(pm->hparams.enc_att_chunk_right_choices));
int req_L_ms = -1, req_C_ms = -1, req_R_ms = -1;
const transcribe_ext * family = stream_params != nullptr ? stream_params->family : nullptr;
if (const transcribe_status st = transcribe_ext_check(family, TRANSCRIBE_EXT_KIND_PARAKEET_BUFFERED_STREAM,
sizeof(struct transcribe_parakeet_buffered_stream_ext));
st != TRANSCRIBE_OK) {
return st;
}
if (family != nullptr) {
const auto * px = reinterpret_cast<const transcribe_parakeet_buffered_stream_ext *>(family);
req_L_ms = px->left_ms;
req_C_ms = px->chunk_ms;
req_R_ms = px->right_ms;
}
const int safe_frame_ms = std::max(frame_ms, 1);
auto ms_to_exact_frames = [&](int req_ms, int default_frames, int * out_frames) -> bool {
if (req_ms == -1) {
*out_frames = default_frames;
return true;
}
if (req_ms < -1) {
return false;
}
if (req_ms % safe_frame_ms != 0) {
return false;
}
*out_frames = req_ms / safe_frame_ms;
return true;
};
int L_frames = 0, C_frames = 0, R_frames = 0;
if (!ms_to_exact_frames(req_L_ms, default_L, &L_frames) || !ms_to_exact_frames(req_C_ms, default_C, &C_frames) ||
!ms_to_exact_frames(req_R_ms, default_R, &R_frames)) {
log_msg(TRANSCRIBE_LOG_LEVEL_ERROR,
"parakeet buffered: requested (L, C, R)_ms = "
"(%d, %d, %d) is invalid. Use -1 on any field to "
"select the model default; otherwise the value "
"must be 0 or a positive exact multiple of the "
"%d ms encoder frame.",
req_L_ms, req_C_ms, req_R_ms, safe_frame_ms);
return TRANSCRIBE_ERR_INVALID_ARG;
}
auto contains = [](const std::vector<int32_t> & v, int x) -> bool {
for (auto y : v) {
if (y == x) {
return true;
}
}
return false;
};
if (!contains(pm->hparams.enc_att_chunk_left_choices, L_frames) ||
!contains(pm->hparams.enc_att_chunk_chunk_choices, C_frames) ||
!contains(pm->hparams.enc_att_chunk_right_choices, R_frames)) {
std::string allowed = "L=";
for (auto v : pm->hparams.enc_att_chunk_left_choices) {
allowed += std::to_string(v) + ",";
}
allowed += " C=";
for (auto v : pm->hparams.enc_att_chunk_chunk_choices) {
allowed += std::to_string(v) + ",";
}
allowed += " R=";
for (auto v : pm->hparams.enc_att_chunk_right_choices) {
allowed += std::to_string(v) + ",";
}
log_msg(TRANSCRIBE_LOG_LEVEL_ERROR,
"parakeet buffered: requested (L, C, R) = (%d, %d, %d) "
"encoder frames not in model menu. Allowed %s",
L_frames, C_frames, R_frames, allowed.c_str());
return TRANSCRIBE_ERR_INVALID_ARG;
}
if (C_frames < 1) {
return TRANSCRIBE_ERR_INVALID_ARG;
}
*out_L_frames = L_frames;
*out_C_frames = C_frames;
*out_R_frames = R_frames;
return TRANSCRIBE_OK;
}
transcribe_status resolve_cache_aware_stream_geom(const ParakeetModel * pm,
const transcribe_stream_params * stream_params,
int * out_chosen_left,
int * out_chosen_right) {
int chosen_right = pm->hparams.enc_att_context_right;
int chosen_left = pm->hparams.enc_att_context_left;
const transcribe_ext * family = stream_params != nullptr ? stream_params->family : nullptr;
if (const transcribe_status st = transcribe_ext_check(family, TRANSCRIBE_EXT_KIND_PARAKEET_STREAM,
sizeof(struct transcribe_parakeet_stream_ext));
st != TRANSCRIBE_OK) {
return st;
}
if (family != nullptr) {
const auto * px = reinterpret_cast<const transcribe_parakeet_stream_ext *>(family);
const int requested = px->att_context_right;
if (requested < -1) {
log_msg(TRANSCRIBE_LOG_LEVEL_ERROR,
"parakeet: att_context_right=%d is invalid; "
"use -1 for the model default or >=0 to pick "
"an entry from the model's training menu",
requested);
return TRANSCRIBE_ERR_INVALID_ARG;
}
if (requested >= 0) {
bool matched = false;
for (const auto & p : pm->hparams.enc_att_context_size_choices) {
if (p.second == requested) {
chosen_right = p.second;
chosen_left = p.first;
matched = true;
break;
}
}
if (!matched) {
std::string available;
for (const auto & p : pm->hparams.enc_att_context_size_choices) {
available += std::to_string(p.second) + " ";
}
log_msg(TRANSCRIBE_LOG_LEVEL_ERROR,
"parakeet: requested att_context_right=%d "
"not in model's training menu; available: %s",
requested, available.c_str());
return TRANSCRIBE_ERR_INVALID_ARG;
}
}
}
*out_chosen_left = chosen_left;
*out_chosen_right = chosen_right;
return TRANSCRIBE_OK;
}
transcribe_status stream_validate(const transcribe_session * session,
const transcribe_run_params * ,
const transcribe_stream_params * stream_params) {
const auto * pc = static_cast<const ParakeetSession *>(session);
const auto * pm = static_cast<const ParakeetModel *>(pc->model);
if (pm == nullptr || pm->plan.scheduler_list.empty()) {
return TRANSCRIBE_ERR_INVALID_ARG;
}
const bool is_chunked_limited =
(pm->hparams.enc_att_context_style == ParakeetHParams::AttContextStyle::ChunkedLimited);
const bool is_chunked_with_rc =
(pm->hparams.enc_att_context_style == ParakeetHParams::AttContextStyle::ChunkedLimitedWithRc);
if (!is_chunked_limited && !is_chunked_with_rc) {
return TRANSCRIBE_ERR_NOT_IMPLEMENTED;
}
if (is_chunked_with_rc) {
int L = 0, C = 0, R = 0;
return resolve_buffered_stream_geom(pm, stream_params, &L, &C, &R);
}
int left = 0, right = 0;
return resolve_cache_aware_stream_geom(pm, stream_params, &left, &right);
}
transcribe_status stream_begin(transcribe_session * session,
const transcribe_run_params * run_params,
const transcribe_stream_params * stream_params) {
auto * pc = static_cast<ParakeetSession *>(session);
auto * pm = static_cast<ParakeetModel *>(pc->model);
if (pm == nullptr || pm->plan.scheduler_list.empty()) {
return TRANSCRIBE_ERR_INVALID_ARG;
}
transcribe::debug::init();
const bool is_chunked_limited =
(pm->hparams.enc_att_context_style == ParakeetHParams::AttContextStyle::ChunkedLimited);
const bool is_chunked_with_rc =
(pm->hparams.enc_att_context_style == ParakeetHParams::AttContextStyle::ChunkedLimitedWithRc);
if (!is_chunked_limited && !is_chunked_with_rc) {
return TRANSCRIBE_ERR_NOT_IMPLEMENTED;
}
if (is_chunked_with_rc) {
int L_frames = 0, C_frames = 0, R_frames = 0;
if (const transcribe_status st =
resolve_buffered_stream_geom(pm, stream_params, &L_frames, &C_frames, &R_frames);
st != TRANSCRIBE_OK) {
return st;
}
const int subsampling_factor = pm->hparams.enc_subsampling_factor;
const int hop = pm->hparams.fe_hop_length;
const int samples_per_frame = subsampling_factor * hop;
pc->buf_left_frames = L_frames;
pc->buf_chunk_frames = C_frames;
pc->buf_right_frames = R_frames;
pc->buf_samples_left = L_frames * samples_per_frame;
pc->buf_samples_chunk = C_frames * samples_per_frame;
pc->buf_samples_right = R_frames * samples_per_frame;
pc->buf_next_audio_read = 0;
pc->buf_ctx_left = 0;
pc->buf_ctx_chunk = 0;
pc->buf_ctx_right = 0;
pc->buf_initialized = false;
pc->buf_chunk_step = 0;
pc->buf_active = true;
pc->stream_pcm_buffer.clear();
pc->raw_tokens.clear();
pc->stream_run_params = *run_params;
reset_streaming_decoder_state(pc, pm);
pc->stream_dec_state.frame_offset = 0;
return TRANSCRIBE_OK;
}
pc->buf_active = false;
int chosen_left = 0, chosen_right = 0;
if (const transcribe_status st = resolve_cache_aware_stream_geom(pm, stream_params, &chosen_left, &chosen_right);
st != TRANSCRIBE_OK) {
return st;
}
pc->stream_pcm_buffer.clear();
pc->raw_tokens.clear(); pc->stream_run_params = *run_params;
if (const transcribe_status st = init_streaming_caches(pc, pm); st != TRANSCRIBE_OK) {
return st;
}
zero_streaming_caches(pc);
reset_streaming_decoder_state(pc, pm);
const int subsampling_factor = pm->hparams.enc_subsampling_factor;
int pre_encode_cache_size = pm->hparams.enc_stream_pre_encode_cache_size;
if (pre_encode_cache_size <= 0) {
pre_encode_cache_size = subsampling_factor + 1;
}
int drop_extra_subsequent = pm->hparams.enc_stream_drop_extra_pre_encoded;
if (drop_extra_subsequent <= 0 && pre_encode_cache_size >= 1) {
drop_extra_subsequent = 1 + (pre_encode_cache_size - 1) / std::max(subsampling_factor, 1);
}
int sampling_frames_first = pm->hparams.enc_stream_sampling_frames_first;
if (sampling_frames_first <= 0) {
sampling_frames_first = subsampling_factor;
}
const int chunk_size_subsequent = subsampling_factor * (1 + chosen_right);
const int chunk_size_first = sampling_frames_first + subsampling_factor * chosen_right;
pc->stream_caches.att_context_right = chosen_right;
pc->stream_caches.att_context_left = chosen_left;
pc->stream_caches.chunk_size_first = chunk_size_first;
pc->stream_caches.chunk_size_subsequent = chunk_size_subsequent;
pc->stream_caches.mel_fed_first = chunk_size_first;
pc->stream_caches.mel_fed_subsequent = chunk_size_subsequent + pre_encode_cache_size;
pc->stream_caches.drop_extra_first = 0;
pc->stream_caches.drop_extra_subsequent = drop_extra_subsequent;
pc->stream_caches.is_first_chunk = true;
pc->stream_caches.chunk_step = 0;
return TRANSCRIBE_OK;
}
transcribe_status stream_feed(transcribe_session * session,
const float * pcm,
int n_samples,
transcribe_stream_update * update) {
auto * pc = static_cast<ParakeetSession *>(session);
auto * pm = static_cast<ParakeetModel *>(pc->model);
if (pc->poll_abort()) {
return TRANSCRIBE_ERR_ABORTED;
}
pc->stream_pcm_buffer.insert(pc->stream_pcm_buffer.end(), pcm, pcm + n_samples);
pc->stream_audio_input_us += samples_to_us(n_samples);
const int prev_n_tokens = static_cast<int>(pc->raw_tokens.size());
if (pm == nullptr || pm->plan.scheduler_list.empty()) {
return TRANSCRIBE_ERR_INVALID_ARG;
}
if (!pm->mel.has_value()) {
return TRANSCRIBE_ERR_INVALID_ARG;
}
if (pc->buf_active) {
while (true) {
if (pc->poll_abort()) {
return TRANSCRIBE_ERR_ABORTED;
}
const int64_t num_new = pc->buf_initialized ? static_cast<int64_t>(pc->buf_samples_chunk) :
static_cast<int64_t>(pc->buf_samples_chunk) +
static_cast<int64_t>(pc->buf_samples_right);
const int64_t need_end = pc->buf_next_audio_read + num_new;
if (need_end > static_cast<int64_t>(pc->stream_pcm_buffer.size())) {
break;
}
if (const transcribe_status st = emit_buffered_chunk(pc, pm, num_new, false);
st != TRANSCRIBE_OK) {
return st;
}
}
const bool tokens_changed = static_cast<int>(pc->raw_tokens.size()) != prev_n_tokens;
if (tokens_changed) {
rebuild_streaming_result_text(pc, pm);
pc->n_committed_tokens = static_cast<int>(pc->tokens.size());
pc->n_committed_words = 0;
pc->n_committed_segments = 0;
pc->stream_revision += 1;
pc->stream_audio_committed_us =
pc->buf_next_audio_read * 1000000LL / std::max<int64_t>(pm->hparams.fe_sample_rate, 1);
}
if (update != nullptr) {
update->result_changed = tokens_changed;
update->revision = pc->stream_revision;
update->input_received_ms = us_to_ms(pc->stream_audio_input_us);
update->audio_committed_ms = us_to_ms(pc->stream_audio_committed_us);
update->buffered_ms = us_to_ms(pc->stream_audio_input_us - pc->stream_audio_committed_us);
}
return TRANSCRIBE_OK;
}
const int64_t t_mel_start = ggml_time_us();
int mel_n_mels = 0;
int mel_n_frames = 0;
if (const transcribe_status mst = pm->mel->compute(pc->stream_pcm_buffer.data(), pc->stream_pcm_buffer.size(),
pc->mel_buf, mel_n_mels, mel_n_frames, pc->n_threads);
mst != TRANSCRIBE_OK) {
if (mst == TRANSCRIBE_ERR_INVALID_ARG) {
if (update != nullptr) {
update->result_changed = false;
update->revision = pc->stream_revision;
update->input_received_ms = us_to_ms(pc->stream_audio_input_us);
update->audio_committed_ms = us_to_ms(pc->stream_audio_committed_us);
update->buffered_ms = us_to_ms(pc->stream_audio_input_us - pc->stream_audio_committed_us);
}
return TRANSCRIBE_OK;
}
return mst;
}
pc->t_mel_us += ggml_time_us() - t_mel_start;
const int pre_encode_cache_size = pm->hparams.enc_stream_pre_encode_cache_size > 0 ?
pm->hparams.enc_stream_pre_encode_cache_size :
pm->hparams.enc_subsampling_factor + 1;
const int hop = pm->hparams.fe_hop_length;
const int64_t pcm_start_frame = pc->stream_caches.pcm_start_sample / hop;
while (true) {
if (pc->poll_abort()) {
return TRANSCRIBE_ERR_ABORTED;
}
const int64_t consumed = pc->stream_caches.mel_frames_consumed;
const int64_t avail = pcm_start_frame + static_cast<int64_t>(mel_n_frames);
const bool is_first = pc->stream_caches.is_first_chunk;
const int chunk_advance =
is_first ? pc->stream_caches.chunk_size_first : pc->stream_caches.chunk_size_subsequent;
const int mel_fed = is_first ? pc->stream_caches.mel_fed_first : pc->stream_caches.mel_fed_subsequent;
const int drop_extra = is_first ? pc->stream_caches.drop_extra_first : pc->stream_caches.drop_extra_subsequent;
const int prepend_frames = mel_fed - chunk_advance;
const int right_edge_margin =
(pm->hparams.fe_n_fft / 2 + pm->hparams.fe_hop_length - 1) / pm->hparams.fe_hop_length;
if (avail - consumed < chunk_advance + right_edge_margin) {
break;
}
std::vector<float> chunk(static_cast<size_t>(mel_n_mels) * static_cast<size_t>(mel_fed));
auto mel_at = [&](int m, int64_t abs_t) -> float {
const int64_t rel = abs_t - pcm_start_frame;
return pc->mel_buf[static_cast<size_t>(m) * mel_n_frames + static_cast<size_t>(rel)];
};
for (int m = 0; m < mel_n_mels; ++m) {
for (int h = 0; h < prepend_frames; ++h) {
const int64_t frame_idx = consumed - prepend_frames + h;
const float val = (frame_idx < 0) ? 0.0f : mel_at(m, frame_idx);
chunk[static_cast<size_t>(m) * mel_fed + h] = val;
}
for (int n = 0; n < chunk_advance; ++n) {
const float val = mel_at(m, consumed + n);
chunk[static_cast<size_t>(m) * mel_fed + prepend_frames + n] = val;
}
}
pc->stream_caches.is_first_chunk = false;
(void) pre_encode_cache_size;
const int64_t t_enc_start = ggml_time_us();
if (const transcribe_status st = emit_streaming_chunk(pc, pm, chunk.data(), mel_fed, drop_extra, chunk_advance);
st != TRANSCRIBE_OK) {
return st;
}
pc->t_encode_us += ggml_time_us() - t_enc_start;
}
const bool tokens_changed = static_cast<int>(pc->raw_tokens.size()) != prev_n_tokens;
if (tokens_changed) {
rebuild_streaming_result_text(pc, pm);
pc->n_committed_tokens = static_cast<int>(pc->tokens.size());
pc->n_committed_words = 0;
pc->n_committed_segments = 0;
pc->stream_revision += 1;
}
{
const int pad = pm->hparams.fe_n_fft / 2;
const int prepend_max = pc->stream_caches.mel_fed_subsequent - pc->stream_caches.chunk_size_subsequent;
const int64_t earliest_frame_needed = pc->stream_caches.mel_frames_consumed - static_cast<int64_t>(prepend_max);
const int64_t earliest_sample_needed = earliest_frame_needed * static_cast<int64_t>(hop);
const int64_t keep_from = earliest_sample_needed - static_cast<int64_t>(pad);
if (keep_from > pc->stream_caches.pcm_start_sample) {
const int64_t drop_raw = keep_from - pc->stream_caches.pcm_start_sample;
const int64_t drop_aligned = (drop_raw / static_cast<int64_t>(hop)) * static_cast<int64_t>(hop);
if (drop_aligned > 0 && drop_aligned <= static_cast<int64_t>(pc->stream_pcm_buffer.size())) {
pc->stream_pcm_buffer.erase(pc->stream_pcm_buffer.begin(),
pc->stream_pcm_buffer.begin() + static_cast<ptrdiff_t>(drop_aligned));
pc->stream_caches.pcm_start_sample += drop_aligned;
}
}
}
pc->stream_audio_committed_us = pc->stream_caches.mel_frames_consumed *
static_cast<int64_t>(pm->hparams.fe_hop_length) * 1000000 /
static_cast<int64_t>(pm->hparams.fe_sample_rate);
if (update != nullptr) {
update->result_changed = tokens_changed;
update->revision = pc->stream_revision;
update->input_received_ms = us_to_ms(pc->stream_audio_input_us);
update->audio_committed_ms = us_to_ms(pc->stream_audio_committed_us);
update->buffered_ms = us_to_ms(pc->stream_audio_input_us - pc->stream_audio_committed_us);
}
return TRANSCRIBE_OK;
}
transcribe_status stream_finalize(transcribe_session * session, transcribe_stream_update * update) {
auto * pc = static_cast<ParakeetSession *>(session);
auto * pm = static_cast<ParakeetModel *>(pc->model);
if (pm == nullptr || pm->plan.scheduler_list.empty()) {
return TRANSCRIBE_ERR_INVALID_ARG;
}
if (!pm->mel.has_value()) {
return TRANSCRIBE_ERR_INVALID_ARG;
}
const int prev_n_tokens = static_cast<int>(pc->raw_tokens.size());
if (pc->buf_active) {
const int64_t total = static_cast<int64_t>(pc->stream_pcm_buffer.size());
if (pc->buf_next_audio_read < total) {
if (pc->poll_abort()) {
return TRANSCRIBE_ERR_ABORTED;
}
const int64_t num_new = total - pc->buf_next_audio_read;
if (const transcribe_status st = emit_buffered_chunk(pc, pm, num_new, true);
st != TRANSCRIBE_OK) {
return st;
}
}
const bool tokens_changed = static_cast<int>(pc->raw_tokens.size()) != prev_n_tokens;
if (tokens_changed || !pc->has_result) {
rebuild_streaming_result_text(pc, pm);
}
pc->stream_audio_committed_us = pc->stream_audio_input_us;
pc->n_committed_tokens = static_cast<int>(pc->tokens.size());
pc->n_committed_words = static_cast<int>(pc->words.size());
pc->n_committed_segments = static_cast<int>(pc->segments.size());
pc->stream_revision += 1;
if (update != nullptr) {
update->result_changed = pc->has_result;
update->revision = pc->stream_revision;
update->input_received_ms = us_to_ms(pc->stream_audio_input_us);
update->audio_committed_ms = us_to_ms(pc->stream_audio_committed_us);
update->buffered_ms = 0;
}
return TRANSCRIBE_OK;
}
if (!pc->stream_pcm_buffer.empty()) {
int mel_n_mels = 0;
int mel_n_frames = 0;
const transcribe_status mst = pm->mel->compute(pc->stream_pcm_buffer.data(), pc->stream_pcm_buffer.size(),
pc->mel_buf, mel_n_mels, mel_n_frames, pc->n_threads);
if (mst != TRANSCRIBE_OK && mst != TRANSCRIBE_ERR_INVALID_ARG) {
return mst;
}
if (mst == TRANSCRIBE_OK) {
const int hop = pm->hparams.fe_hop_length;
const int64_t pcm_start_frame = pc->stream_caches.pcm_start_sample / hop;
const int64_t consumed = pc->stream_caches.mel_frames_consumed;
const int64_t avail = pcm_start_frame + static_cast<int64_t>(mel_n_frames);
const int64_t remaining = avail - consumed;
if (remaining > 0) {
const bool is_first = pc->stream_caches.is_first_chunk;
const int drop_extra =
is_first ? pc->stream_caches.drop_extra_first : pc->stream_caches.drop_extra_subsequent;
const int mel_fed_full =
is_first ? pc->stream_caches.mel_fed_first : pc->stream_caches.mel_fed_subsequent;
const int chunk_advance_full =
is_first ? pc->stream_caches.chunk_size_first : pc->stream_caches.chunk_size_subsequent;
const int prepend_frames = mel_fed_full - chunk_advance_full;
const int new_take = static_cast<int>(remaining);
const int mel_fed = prepend_frames + new_take;
std::vector<float> chunk(static_cast<size_t>(mel_n_mels) * static_cast<size_t>(mel_fed), 0.0f);
auto mel_at = [&](int m, int64_t abs_t) -> float {
const int64_t rel = abs_t - pcm_start_frame;
return pc->mel_buf[static_cast<size_t>(m) * mel_n_frames + static_cast<size_t>(rel)];
};
for (int m = 0; m < mel_n_mels; ++m) {
for (int h = 0; h < prepend_frames; ++h) {
const int64_t frame_idx = consumed - prepend_frames + h;
const float val = (frame_idx < 0) ? 0.0f : mel_at(m, frame_idx);
chunk[static_cast<size_t>(m) * mel_fed + h] = val;
}
for (int n = 0; n < new_take; ++n) {
const float val = mel_at(m, consumed + n);
chunk[static_cast<size_t>(m) * mel_fed + prepend_frames + n] = val;
}
}
pc->stream_caches.is_first_chunk = false;
if (const transcribe_status st =
emit_streaming_chunk(pc, pm, chunk.data(), mel_fed, drop_extra, new_take);
st != TRANSCRIBE_OK) {
return st;
}
}
}
}
const bool tokens_changed = static_cast<int>(pc->raw_tokens.size()) != prev_n_tokens;
if (tokens_changed || !pc->has_result) {
rebuild_streaming_result_text(pc, pm);
}
pc->stream_audio_committed_us = pc->stream_audio_input_us;
pc->n_committed_tokens = static_cast<int>(pc->tokens.size());
pc->n_committed_words = static_cast<int>(pc->words.size());
pc->n_committed_segments = static_cast<int>(pc->segments.size());
pc->stream_revision += 1;
if (update != nullptr) {
update->result_changed = pc->has_result;
update->revision = pc->stream_revision;
update->input_received_ms = us_to_ms(pc->stream_audio_input_us);
update->audio_committed_ms = us_to_ms(pc->stream_audio_committed_us);
update->buffered_ms = 0;
}
return TRANSCRIBE_OK;
}
void stream_reset(transcribe_session * session) {
auto * pc = static_cast<ParakeetSession *>(session);
pc->stream_pcm_buffer.clear(); }
bool accepts_ext_kind(const transcribe_model * model, transcribe_ext_slot slot, uint32_t kind) {
if (model == nullptr) {
return false;
}
if (slot != TRANSCRIBE_EXT_SLOT_STREAM) {
return false;
}
const auto * pm = static_cast<const ParakeetModel *>(model);
switch (pm->hparams.enc_att_context_style) {
case ParakeetHParams::AttContextStyle::ChunkedLimited:
return kind == TRANSCRIBE_EXT_KIND_PARAKEET_STREAM;
case ParakeetHParams::AttContextStyle::ChunkedLimitedWithRc:
return kind == TRANSCRIBE_EXT_KIND_PARAKEET_BUFFERED_STREAM;
case ParakeetHParams::AttContextStyle::Regular:
return false;
}
return false;
}
}
extern const Arch arch = {
"parakeet",
load,
init_context,
run,
run_batch,
stream_validate,
stream_begin,
stream_feed,
stream_finalize,
stream_reset,
accepts_ext_kind,
};
}
extern "C" void transcribe_parakeet_stream_ext_init(struct transcribe_parakeet_stream_ext * p) {
if (p == nullptr) {
return;
}
std::memset(p, 0, sizeof(*p));
p->ext.size = sizeof(*p);
p->ext.kind = TRANSCRIBE_EXT_KIND_PARAKEET_STREAM;
p->att_context_right = -1; }
extern "C" void transcribe_parakeet_buffered_stream_ext_init(struct transcribe_parakeet_buffered_stream_ext * p) {
if (p == nullptr) {
return;
}
std::memset(p, 0, sizeof(*p));
p->ext.size = sizeof(*p);
p->ext.kind = TRANSCRIBE_EXT_KIND_PARAKEET_BUFFERED_STREAM;
p->left_ms = -1; p->chunk_ms = -1;
p->right_ms = -1;
}