#include "encoder.h"
#include "ggml.h"
#include "transcribe-debug.h"
#include "transcribe-log.h"
#include <algorithm>
#include <cmath>
#include <cstdio>
#include <cstring>
#include <limits>
#include <vector>
namespace transcribe::qwen3_asr {
int32_t aftercnn_len(int32_t mel_len) {
auto step = [](int32_t L) {
return (L + 1) / 2;
};
return step(step(step(mel_len)));
}
EncoderTiming compute_encoder_timing(int32_t n_mel_frames, const QwenAsrHParams & hp) {
EncoderTiming t{};
t.n_mel_frames = n_mel_frames;
t.mel_per_chunk = hp.enc_n_window * 2;
if (n_mel_frames <= 0 || t.mel_per_chunk <= 0) {
return t;
}
t.n_chunks = (n_mel_frames + t.mel_per_chunk - 1) / t.mel_per_chunk;
int32_t last = n_mel_frames - (t.n_chunks - 1) * t.mel_per_chunk;
if (last == 0) {
last = t.mel_per_chunk;
}
t.last_chunk_real_mel = last;
t.per_chunk_aftercnn = aftercnn_len(t.mel_per_chunk);
t.last_chunk_aftercnn = aftercnn_len(last);
t.T_enc_padded = t.n_chunks * t.per_chunk_aftercnn;
t.T_enc = (t.n_chunks - 1) * t.per_chunk_aftercnn + t.last_chunk_aftercnn;
t.aftercnn_lens_total = aftercnn_len(n_mel_frames);
return t;
}
std::vector<float> build_sinusoid_pe(int32_t d_model, int32_t length, double max_timescale) {
std::vector<float> pe(static_cast<size_t>(d_model) * length, 0.0f);
if (d_model <= 0 || length <= 0 || (d_model % 2) != 0) {
return pe;
}
const int32_t half = d_model / 2;
const double log_ts = std::log(max_timescale) / (half - 1);
for (int32_t p = 0; p < length; ++p) {
for (int32_t k = 0; k < half; ++k) {
const double inv_ts = std::exp(-log_ts * k);
const double scaled = static_cast<double>(p) * inv_ts;
pe[static_cast<size_t>(p) * d_model + k] = static_cast<float>(std::sin(scaled));
pe[static_cast<size_t>(p) * d_model + k + half] = static_cast<float>(std::cos(scaled));
}
}
return pe;
}
namespace {
constexpr float kLayerNormEps = 1e-5f;
ggml_tensor * named(ggml_tensor * t, const char * name) {
if (t != nullptr && name != nullptr) {
ggml_set_name(t, name);
}
return t;
}
ggml_tensor * add_conv_bias(ggml_context * ctx, ggml_tensor * x, ggml_tensor * bias) {
ggml_tensor * b4 = ggml_reshape_4d(ctx, bias, 1, 1, bias->ne[0], 1);
return ggml_add(ctx, x, b4);
}
ggml_tensor * layer_norm(ggml_context * ctx, ggml_tensor * x, ggml_tensor * gamma, ggml_tensor * beta) {
ggml_tensor * y = ggml_norm(ctx, x, kLayerNormEps);
y = ggml_mul(ctx, y, gamma);
if (beta != nullptr) {
y = ggml_add(ctx, y, beta);
}
return y;
}
ggml_tensor * linear(ggml_context * ctx, ggml_tensor * x, ggml_tensor * w, ggml_tensor * b) {
ggml_tensor * y = ggml_mul_mat(ctx, w, x);
if (b != nullptr) {
y = ggml_add(ctx, y, b);
}
return y;
}
ggml_tensor * subsample_group(ggml_context * ctx,
const QwenAsrWeights & weights,
int64_t ds_h,
int64_t T_per_chunk,
ggml_tensor * mel) {
const int64_t n = mel->ne[3];
ggml_tensor * x = mel;
x = ggml_conv_2d(ctx, weights.enc_subsample.conv0_w, x,
2, 2, 1, 1, 1, 1);
x = add_conv_bias(ctx, x, weights.enc_subsample.conv0_b);
x = ggml_gelu_erf(ctx, x);
x = ggml_conv_2d(ctx, weights.enc_subsample.conv1_w, x, 2, 2, 1, 1, 1, 1);
x = add_conv_bias(ctx, x, weights.enc_subsample.conv1_b);
x = ggml_gelu_erf(ctx, x);
x = ggml_conv_2d(ctx, weights.enc_subsample.conv2_w, x, 2, 2, 1, 1, 1, 1);
x = add_conv_bias(ctx, x, weights.enc_subsample.conv2_b);
x = ggml_gelu_erf(ctx, x);
const int64_t W_ds = x->ne[0];
const int64_t H_ds = x->ne[1];
if (W_ds != T_per_chunk) {
log_msg(TRANSCRIBE_LOG_LEVEL_ERROR,
"qwen3_asr encoder: post-conv W=%lld does not match "
"per_chunk_aftercnn=%lld",
static_cast<long long>(W_ds), static_cast<long long>(T_per_chunk));
return nullptr;
}
x = ggml_permute(ctx, x, 2, 0, 1, 3);
x = ggml_cont(ctx, x);
x = ggml_reshape_3d(ctx, x, H_ds * ds_h, T_per_chunk, n);
x = ggml_mul_mat(ctx, weights.enc_subsample.conv_out, x);
return x; }
ggml_tensor * subsample_chunks(ggml_context * ctx,
const QwenAsrWeights & weights,
int64_t ds_h,
int64_t T_per_chunk,
ggml_tensor * mel) {
const int64_t n_chunks = mel->ne[3];
ggml_tensor * out = nullptr;
for (int64_t c0 = 0; c0 < n_chunks; c0 += kConvChunkGroup) {
const int64_t n = std::min<int64_t>(kConvChunkGroup, n_chunks - c0);
ggml_tensor * g = (n == n_chunks) ? mel :
ggml_view_4d(ctx, mel, mel->ne[0], mel->ne[1], mel->ne[2], n, mel->nb[1],
mel->nb[2], mel->nb[3], static_cast<size_t>(c0) * mel->nb[3]);
ggml_tensor * y = subsample_group(ctx, weights, ds_h, T_per_chunk, g);
if (y == nullptr) {
return nullptr;
}
out = (out == nullptr) ? y : ggml_concat(ctx, out, y, 2);
}
return out;
}
ggml_tensor * build_enc_block(ggml_context * ctx,
ggml_tensor * x,
ggml_tensor * mask,
const QwenAsrEncBlock & w,
int d_model,
int n_heads,
bool use_flash) {
const int head_dim = d_model / n_heads;
const float scale = 1.0f / std::sqrt(static_cast<float>(head_dim));
const int64_t T = x->ne[1];
const int64_t B = x->ne[2];
ggml_tensor * x_norm = layer_norm(ctx, x, w.norm_attn_w, w.norm_attn_b);
ggml_tensor * q = linear(ctx, x_norm, w.attn_q_w, w.attn_q_b);
ggml_tensor * k = linear(ctx, x_norm, w.attn_k_w, w.attn_k_b);
ggml_tensor * v = linear(ctx, x_norm, w.attn_v_w, w.attn_v_b);
q = ggml_reshape_4d(ctx, q, head_dim, n_heads, T, B);
k = ggml_reshape_4d(ctx, k, head_dim, n_heads, T, B);
v = ggml_reshape_4d(ctx, v, head_dim, n_heads, T, B);
q = ggml_cont(ctx, ggml_permute(ctx, q, 0, 2, 1, 3));
k = ggml_cont(ctx, ggml_permute(ctx, k, 0, 2, 1, 3));
v = ggml_cont(ctx, ggml_permute(ctx, v, 0, 2, 1, 3));
ggml_tensor * o;
if (use_flash) {
o = ggml_flash_attn_ext(ctx, q, k, v, mask, scale, 0.0f,
0.0f);
o = (B == 1) ? ggml_reshape_2d(ctx, o, d_model, T) : ggml_reshape_3d(ctx, o, d_model, T, B);
} else {
ggml_tensor * kq = ggml_mul_mat(ctx, k, q);
ggml_tensor * kq_soft = ggml_soft_max_ext(ctx, kq, mask, scale, 0.0f);
ggml_tensor * v_t = ggml_cont(ctx, ggml_permute(ctx, v, 1, 0, 2, 3));
o = ggml_mul_mat(ctx, v_t, kq_soft);
o = ggml_cont(ctx, ggml_permute(ctx, o, 0, 2, 1, 3));
o = (B == 1) ? ggml_reshape_2d(ctx, o, d_model, T) : ggml_reshape_3d(ctx, o, d_model, T, B);
}
o = linear(ctx, o, w.attn_out_w, w.attn_out_b);
x = ggml_add(ctx, x, o);
ggml_tensor * y = layer_norm(ctx, x, w.norm_ffn_w, w.norm_ffn_b);
y = linear(ctx, y, w.fc1_w, w.fc1_b);
y = ggml_gelu_erf(ctx, y);
y = linear(ctx, y, w.fc2_w, w.fc2_b);
return ggml_add(ctx, x, y);
}
}
SubsampleBuild build_subsample_graph(ggml_context * ctx,
const QwenAsrWeights & weights,
const QwenAsrHParams & hp,
int n_chunks) {
SubsampleBuild sb{};
if (ctx == nullptr || n_chunks <= 0) {
log_msg(TRANSCRIBE_LOG_LEVEL_ERROR, "qwen3_asr subsample: invalid arg (ctx=%p, n_chunks=%d)",
static_cast<void *>(ctx), n_chunks);
return sb;
}
const int64_t mel_per_chunk = hp.enc_n_window * 2;
const int64_t T_per_chunk = aftercnn_len(static_cast<int32_t>(mel_per_chunk));
sb.mel_in = ggml_new_tensor_4d(ctx, GGML_TYPE_F32, mel_per_chunk, hp.enc_num_mel_bins, 1, n_chunks);
named(sb.mel_in, "enc.mel_in");
ggml_set_input(sb.mel_in);
sb.out = subsample_chunks(ctx, weights, hp.enc_downsample_hidden, T_per_chunk, sb.mel_in);
if (sb.out == nullptr) {
return sb;
}
named(sb.out, "enc.subsample.pass");
ggml_set_output(sb.out);
const int n_groups = (n_chunks + kConvChunkGroup - 1) / kConvChunkGroup;
sb.graph = ggml_new_graph_custom(ctx, 64 + 48 * n_groups, false);
if (sb.graph == nullptr) {
log_msg(TRANSCRIBE_LOG_LEVEL_ERROR, "qwen3_asr subsample: ggml_new_graph_custom failed");
return sb;
}
ggml_build_forward_expand(sb.graph, sb.out);
return sb;
}
EncoderBuild build_encoder_graph(ggml_context * ctx,
const QwenAsrWeights & weights,
const QwenAsrHParams & hp,
const EncoderTiming & timing,
bool use_flash) {
EncoderBuild eb{};
eb.timing = timing;
if (ctx == nullptr || timing.n_chunks <= 0) {
log_msg(TRANSCRIBE_LOG_LEVEL_ERROR,
"qwen3_asr encoder: invalid arg "
"(ctx=%p, n_chunks=%d)",
static_cast<void *>(ctx), timing.n_chunks);
return eb;
}
const int64_t d_model = hp.enc_d_model;
const int64_t n_heads = hp.enc_n_heads;
const int64_t n_chunks = timing.n_chunks;
const int64_t T_per_chunk = timing.per_chunk_aftercnn;
const int64_t T_enc_padded = timing.T_enc_padded;
const int64_t T_enc = timing.T_enc;
eb.subsample_in = ggml_new_tensor_3d(ctx, GGML_TYPE_F32, d_model, T_per_chunk, n_chunks);
named(eb.subsample_in, "enc.subsample.out");
ggml_set_input(eb.subsample_in);
eb.dumps.subsample_out = eb.subsample_in;
transcribe::debug::mark_tensor_for_dump(eb.subsample_in);
eb.pos_emb_in = ggml_new_tensor_2d(ctx, GGML_TYPE_F32, d_model, T_per_chunk);
named(eb.pos_emb_in, "enc.pos_emb.in");
ggml_set_input(eb.pos_emb_in);
ggml_tensor * x = ggml_add(ctx, eb.subsample_in, eb.pos_emb_in);
x = ggml_cont(ctx, x);
transcribe::debug::mark_tensor_for_dump(x);
x = ggml_reshape_2d(ctx, x, d_model, T_enc_padded);
if (T_enc < T_enc_padded) {
x = ggml_view_2d(ctx, x, d_model, T_enc, x->nb[1], 0);
x = ggml_cont(ctx, x);
}
named(x, "enc.pos_add.out");
eb.dumps.pos_add_out = x;
transcribe::debug::mark_tensor_for_dump(x);
const int n_layers = static_cast<int>(weights.enc_blocks.size());
for (int i = 0; i < n_layers; ++i) {
x = build_enc_block(ctx, x, nullptr, weights.enc_blocks[i], static_cast<int>(d_model),
static_cast<int>(n_heads), use_flash);
if (i == 0) {
named(x, "enc.block.0.out");
eb.dumps.block_0_out = x;
transcribe::debug::mark_tensor_for_dump(x);
} else if (i == n_layers - 1) {
char bname[64];
std::snprintf(bname, sizeof(bname), "enc.block.%d.out", i);
named(x, bname);
eb.dumps.block_last_out = x;
transcribe::debug::mark_tensor_for_dump(x);
}
}
x = layer_norm(ctx, x, weights.enc_head.ln_post_w, weights.enc_head.ln_post_b);
named(x, "enc.ln_post.out");
eb.dumps.ln_post_out = x;
transcribe::debug::mark_tensor_for_dump(x);
x = linear(ctx, x, weights.enc_head.proj1_w, weights.enc_head.proj1_b);
x = ggml_gelu_erf(ctx, x);
x = linear(ctx, x, weights.enc_head.proj2_w, weights.enc_head.proj2_b);
named(x, "enc.proj.out");
eb.dumps.proj_out = x;
transcribe::debug::mark_tensor_for_dump(x);
eb.out = x;
ggml_set_output(eb.out);
eb.graph = ggml_new_graph_custom(ctx, 8192, false);
if (eb.graph == nullptr) {
log_msg(TRANSCRIBE_LOG_LEVEL_ERROR, "qwen3_asr encoder: ggml_new_graph_custom failed");
return eb;
}
ggml_build_forward_expand(eb.graph, eb.out);
if (eb.dumps.subsample_out) {
ggml_build_forward_expand(eb.graph, eb.dumps.subsample_out);
}
if (eb.dumps.pos_add_out) {
ggml_build_forward_expand(eb.graph, eb.dumps.pos_add_out);
}
if (eb.dumps.block_0_out) {
ggml_build_forward_expand(eb.graph, eb.dumps.block_0_out);
}
if (eb.dumps.block_last_out) {
ggml_build_forward_expand(eb.graph, eb.dumps.block_last_out);
}
if (eb.dumps.ln_post_out) {
ggml_build_forward_expand(eb.graph, eb.dumps.ln_post_out);
}
if (eb.dumps.proj_out) {
ggml_build_forward_expand(eb.graph, eb.dumps.proj_out);
}
return eb;
}
EncoderBuildBatched build_encoder_graph_batched(ggml_context * ctx,
const QwenAsrWeights & weights,
const QwenAsrHParams & hp,
int n_chunks_max,
int n_batch,
bool use_flash) {
EncoderBuildBatched eb{};
if (ctx == nullptr || n_chunks_max <= 0 || n_batch <= 0) {
log_msg(TRANSCRIBE_LOG_LEVEL_ERROR,
"qwen3_asr encoder(batched): invalid arg "
"(n_chunks_max=%d, n_batch=%d)",
n_chunks_max, n_batch);
return eb;
}
const int64_t d_model = hp.enc_d_model;
const int64_t n_heads = hp.enc_n_heads;
const int64_t T_per_chunk = aftercnn_len(hp.enc_n_window * 2);
const int64_t B = n_batch;
const int64_t N = B * n_chunks_max; const int64_t T_pad_max = n_chunks_max * T_per_chunk;
eb.n_batch = n_batch;
eb.n_chunks_max = n_chunks_max;
eb.T_per_chunk = static_cast<int>(T_per_chunk);
eb.T_pad_max = static_cast<int>(T_pad_max);
eb.subsample_in = ggml_new_tensor_3d(ctx, GGML_TYPE_F32, d_model, T_per_chunk, N);
named(eb.subsample_in, "enc.subsample.out");
ggml_set_input(eb.subsample_in);
eb.pos_emb_in = ggml_new_tensor_2d(ctx, GGML_TYPE_F32, d_model, T_per_chunk);
named(eb.pos_emb_in, "enc.pos_emb.in");
ggml_set_input(eb.pos_emb_in);
eb.mask_in = ggml_new_tensor_4d(ctx, use_flash ? GGML_TYPE_F16 : GGML_TYPE_F32, T_pad_max, T_pad_max, 1, B);
named(eb.mask_in, "enc.attn_mask.in");
ggml_set_input(eb.mask_in);
ggml_tensor * x = ggml_add(ctx, eb.subsample_in, eb.pos_emb_in);
x = ggml_cont(ctx, x);
x = ggml_reshape_4d(ctx, x, d_model, T_per_chunk, n_chunks_max, B);
x = ggml_reshape_3d(ctx, x, d_model, T_pad_max, B);
const int n_layers = static_cast<int>(weights.enc_blocks.size());
for (int i = 0; i < n_layers; ++i) {
x = build_enc_block(ctx, x, eb.mask_in, weights.enc_blocks[i], static_cast<int>(d_model),
static_cast<int>(n_heads), use_flash);
}
x = layer_norm(ctx, x, weights.enc_head.ln_post_w, weights.enc_head.ln_post_b);
x = linear(ctx, x, weights.enc_head.proj1_w, weights.enc_head.proj1_b);
x = ggml_gelu_erf(ctx, x);
x = linear(ctx, x, weights.enc_head.proj2_w, weights.enc_head.proj2_b);
named(x, "enc.proj.out");
eb.out = x;
ggml_set_output(eb.out);
eb.graph = ggml_new_graph_custom(ctx, 8192, false);
if (eb.graph == nullptr) {
log_msg(TRANSCRIBE_LOG_LEVEL_ERROR, "qwen3_asr encoder(batched): ggml_new_graph_custom failed");
return eb;
}
ggml_build_forward_expand(eb.graph, eb.out);
return eb;
}
}