#include "weights.h"
#include "transcribe-meta.h"
#include "transcribe-weights-util.h"
#include "transcribe-log.h"
#include "ggml.h"
#include "ggml-backend.h"
#include "gguf.h"
#include <cmath>
#include <cstdio>
#include <cstring>
namespace transcribe::medasr {
namespace {
constexpr const char * kFamilyTag = "medasr";
}
transcribe_status read_medasr_hparams(const gguf_context * gguf,
MedAsrHParams & hp)
{
if (gguf == nullptr) return TRANSCRIBE_ERR_INVALID_ARG;
if (auto st = read_required_u32_kv (gguf, "stt.medasr.encoder.n_layers", kFamilyTag, hp.enc_n_layers); st != TRANSCRIBE_OK) return st;
if (auto st = read_required_u32_kv (gguf, "stt.medasr.encoder.hidden", kFamilyTag, hp.enc_hidden); st != TRANSCRIBE_OK) return st;
if (auto st = read_required_u32_kv (gguf, "stt.medasr.encoder.n_heads", kFamilyTag, hp.enc_n_heads); st != TRANSCRIBE_OK) return st;
if (auto st = read_required_u32_kv (gguf, "stt.medasr.encoder.n_kv_heads", kFamilyTag, hp.enc_n_kv_heads); st != TRANSCRIBE_OK) return st;
if (auto st = read_required_u32_kv (gguf, "stt.medasr.encoder.head_dim", kFamilyTag, hp.enc_head_dim); st != TRANSCRIBE_OK) return st;
if (auto st = read_required_u32_kv (gguf, "stt.medasr.encoder.intermediate", kFamilyTag, hp.enc_intermediate); st != TRANSCRIBE_OK) return st;
if (auto st = read_required_string_kv(gguf, "stt.medasr.encoder.hidden_act", kFamilyTag, hp.enc_hidden_act); st != TRANSCRIBE_OK) return st;
if (auto st = read_required_u32_kv (gguf, "stt.medasr.encoder.conv_kernel", kFamilyTag, hp.enc_conv_kernel); st != TRANSCRIBE_OK) return st;
if (auto st = read_required_f32_kv (gguf, "stt.medasr.encoder.conv_residual_w0", kFamilyTag, hp.enc_conv_resid_w0);st != TRANSCRIBE_OK) return st;
if (auto st = read_required_f32_kv (gguf, "stt.medasr.encoder.conv_residual_w1", kFamilyTag, hp.enc_conv_resid_w1);st != TRANSCRIBE_OK) return st;
if (auto st = read_required_f32_kv (gguf, "stt.medasr.encoder.ff_residual_w0", kFamilyTag, hp.enc_ff_resid_w0); st != TRANSCRIBE_OK) return st;
if (auto st = read_required_f32_kv (gguf, "stt.medasr.encoder.ff_residual_w1", kFamilyTag, hp.enc_ff_resid_w1); st != TRANSCRIBE_OK) return st;
if (auto st = read_required_f32_kv (gguf, "stt.medasr.encoder.layer_norm_eps", kFamilyTag, hp.enc_layer_norm_eps); st != TRANSCRIBE_OK) return st;
if (auto st = read_required_u32_kv (gguf, "stt.medasr.encoder.max_pos_emb", kFamilyTag, hp.enc_max_pos_emb); st != TRANSCRIBE_OK) return st;
if (auto st = read_required_f32_kv (gguf, "stt.medasr.encoder.batch_norm_momentum",kFamilyTag, hp.enc_batch_norm_momentum); st != TRANSCRIBE_OK) return st;
if (auto st = read_required_f32_kv (gguf, "stt.medasr.encoder.rope_theta", kFamilyTag, hp.enc_rope_theta); st != TRANSCRIBE_OK) return st;
if (auto st = read_required_string_kv(gguf, "stt.medasr.encoder.rope_type", kFamilyTag, hp.enc_rope_type); st != TRANSCRIBE_OK) return st;
if (auto st = read_required_u32_kv (gguf, "stt.medasr.encoder.num_mel_bins", kFamilyTag, hp.enc_num_mel_bins); st != TRANSCRIBE_OK) return st;
if (auto st = read_required_u32_kv (gguf, "stt.medasr.encoder.sub_kernel", kFamilyTag, hp.enc_sub_kernel); st != TRANSCRIBE_OK) return st;
if (auto st = read_required_u32_kv (gguf, "stt.medasr.encoder.sub_stride", kFamilyTag, hp.enc_sub_stride); st != TRANSCRIBE_OK) return st;
if (auto st = read_required_u32_kv (gguf, "stt.medasr.encoder.sub_channels", kFamilyTag, hp.enc_sub_channels); st != TRANSCRIBE_OK) return st;
if (auto st = read_required_u32_kv (gguf, "stt.medasr.encoder.sub_n_layers", kFamilyTag, hp.enc_sub_n_layers); st != TRANSCRIBE_OK) return st;
if (hp.enc_n_heads <= 0 || hp.enc_hidden % hp.enc_n_heads != 0) {
log_msg(TRANSCRIBE_LOG_LEVEL_ERROR,
"medasr: invariant hidden %% n_heads != 0 "
"(hidden=%d, n_heads=%d)",
hp.enc_hidden, hp.enc_n_heads);
return TRANSCRIBE_ERR_GGUF;
}
if (hp.enc_sub_n_layers != 2 || hp.enc_sub_stride != 2) {
log_msg(TRANSCRIBE_LOG_LEVEL_ERROR,
"medasr: unsupported subsampling shape (n_layers=%d, "
"stride=%d) — only 2 stride-2 convs implemented",
hp.enc_sub_n_layers, hp.enc_sub_stride);
return TRANSCRIBE_ERR_GGUF;
}
if (hp.enc_rope_type != "default") {
log_msg(TRANSCRIBE_LOG_LEVEL_ERROR,
"medasr: unsupported rope_type=%s (expected default)",
hp.enc_rope_type.c_str());
return TRANSCRIBE_ERR_GGUF;
}
if (auto st = read_required_u32_kv(gguf, "stt.medasr.ctc.vocab_size", kFamilyTag, hp.ctc_vocab_size); st != TRANSCRIBE_OK) return st;
if (auto st = read_required_u32_kv(gguf, "stt.medasr.ctc.blank_id", kFamilyTag, hp.ctc_blank_id); st != TRANSCRIBE_OK) return st;
if (auto st = read_required_string_kv(gguf, "stt.frontend.type", kFamilyTag, hp.fe_type); st != TRANSCRIBE_OK) return st;
if (auto st = read_required_u32_kv (gguf, "stt.frontend.sample_rate", kFamilyTag, hp.fe_sample_rate); st != TRANSCRIBE_OK) return st;
if (auto st = read_required_u32_kv (gguf, "stt.frontend.num_mels", kFamilyTag, hp.fe_num_mels); st != TRANSCRIBE_OK) return st;
if (auto st = read_required_u32_kv (gguf, "stt.frontend.n_fft", kFamilyTag, hp.fe_n_fft); st != TRANSCRIBE_OK) return st;
if (auto st = read_required_u32_kv (gguf, "stt.frontend.win_length", kFamilyTag, hp.fe_win_length); st != TRANSCRIBE_OK) return st;
if (auto st = read_required_u32_kv (gguf, "stt.frontend.hop_length", kFamilyTag, hp.fe_hop_length); st != TRANSCRIBE_OK) return st;
if (auto st = read_required_string_kv(gguf, "stt.frontend.window", kFamilyTag, hp.fe_window); st != TRANSCRIBE_OK) return st;
if (auto st = read_required_string_kv(gguf, "stt.frontend.normalize", kFamilyTag, hp.fe_normalize); st != TRANSCRIBE_OK) return st;
if (auto st = read_required_string_kv(gguf, "stt.frontend.pad_mode", kFamilyTag, hp.fe_pad_mode); st != TRANSCRIBE_OK) return st;
if (auto st = read_required_string_kv(gguf, "stt.frontend.mel_norm", kFamilyTag, hp.fe_mel_norm); st != TRANSCRIBE_OK) return st;
if (auto st = read_required_f32_kv (gguf, "stt.frontend.log_clamp_min", kFamilyTag, hp.fe_log_clamp_min); st != TRANSCRIBE_OK) return st;
if (auto st = read_required_f32_kv (gguf, "stt.frontend.mel_lower_hz", kFamilyTag, hp.fe_mel_lower_hz); st != TRANSCRIBE_OK) return st;
if (auto st = read_required_f32_kv (gguf, "stt.frontend.mel_upper_hz", kFamilyTag, hp.fe_mel_upper_hz); st != TRANSCRIBE_OK) return st;
return TRANSCRIBE_OK;
}
#define GET_LN(slot, name) do { \
ggml_tensor * _t = transcribe::weights::find_tensor( \
ctx_meta, (name), {GGML_TYPE_F32}, \
{hp.enc_hidden}, kFamilyTag); \
if (_t == nullptr) return TRANSCRIBE_ERR_GGUF; \
(slot) = _t; \
} while (0)
#define GET_LIN_W(slot, name, in_dim, out_dim) do { \
ggml_tensor * _t = transcribe::weights::find_tensor( \
ctx_meta, (name), {TRANSCRIBE_QUANT_LINEAR_TYPES}, \
{(in_dim), (out_dim)}, kFamilyTag); \
if (_t == nullptr) return TRANSCRIBE_ERR_GGUF; \
(slot) = _t; \
} while (0)
#define GET_LIN_B(slot, name, out_dim) do { \
ggml_tensor * _t = transcribe::weights::find_tensor( \
ctx_meta, (name), {GGML_TYPE_F32}, \
{(out_dim)}, kFamilyTag); \
if (_t == nullptr) return TRANSCRIBE_ERR_GGUF; \
(slot) = _t; \
} while (0)
#define GET_CONV_W(slot, name, k, in_dim, out_dim) do { \
ggml_tensor * _t = transcribe::weights::find_tensor( \
ctx_meta, (name), {TRANSCRIBE_QUANT_CONV_TYPES}, \
{(k), (in_dim), (out_dim)}, kFamilyTag); \
if (_t == nullptr) return TRANSCRIBE_ERR_GGUF; \
(slot) = _t; \
} while (0)
#define GET_F32_1D(slot, name, n) do { \
ggml_tensor * _t = transcribe::weights::find_tensor( \
ctx_meta, (name), {GGML_TYPE_F32}, \
{(n)}, kFamilyTag); \
if (_t == nullptr) return TRANSCRIBE_ERR_GGUF; \
(slot) = _t; \
} while (0)
#define GET_F32_2D(slot, name, n0, n1) do { \
ggml_tensor * _t = transcribe::weights::find_tensor( \
ctx_meta, (name), {GGML_TYPE_F32}, \
{(n0), (n1)}, kFamilyTag); \
if (_t == nullptr) return TRANSCRIBE_ERR_GGUF; \
(slot) = _t; \
} while (0)
using transcribe::weights::lname;
namespace {
struct RawBnTensors {
ggml_tensor * w = nullptr;
ggml_tensor * b = nullptr;
ggml_tensor * mean = nullptr;
ggml_tensor * var = nullptr;
};
std::vector<RawBnTensors> g_raw_bn;
transcribe_status build_block(ggml_context * ctx_meta,
const MedAsrHParams & hp,
int i,
MedAsrBlock & b,
RawBnTensors & bn)
{
const int d = hp.enc_hidden;
const int dff = hp.enc_intermediate;
const int dw_k = hp.enc_conv_kernel;
GET_LN (b.norm_ff1_w, lname("enc.blocks.%d.norm_ff1.weight", i));
GET_LIN_W(b.ff1_up_w, lname("enc.blocks.%d.ff1_up.weight", i), d, dff);
GET_LIN_W(b.ff1_down_w, lname("enc.blocks.%d.ff1_down.weight", i), dff, d);
GET_LN (b.norm_attn_w, lname("enc.blocks.%d.norm_attn.weight", i));
GET_LIN_W(b.attn_q_w, lname("enc.blocks.%d.attn_q.weight", i), d, d);
GET_LIN_W(b.attn_k_w, lname("enc.blocks.%d.attn_k.weight", i), d, d);
GET_LIN_W(b.attn_v_w, lname("enc.blocks.%d.attn_v.weight", i), d, d);
GET_LIN_W(b.attn_o_w, lname("enc.blocks.%d.attn_o.weight", i), d, d);
GET_LN (b.norm_conv_w, lname("enc.blocks.%d.norm_conv.weight", i));
GET_CONV_W(b.conv_pw1_w, lname("enc.blocks.%d.conv_pointwise1.weight", i), 1, d, 2 * d);
GET_CONV_W(b.conv_dw_w, lname("enc.blocks.%d.conv_depthwise.weight", i), dw_k, 1, d);
GET_CONV_W(b.conv_pw2_w, lname("enc.blocks.%d.conv_pointwise2.weight", i), 1, d, d);
GET_F32_1D(bn.w, lname("enc.blocks.%d.conv_bn.weight", i), d);
GET_F32_1D(bn.b, lname("enc.blocks.%d.conv_bn.bias", i), d);
GET_F32_1D(bn.mean, lname("enc.blocks.%d.conv_bn.running_mean", i), d);
GET_F32_1D(bn.var, lname("enc.blocks.%d.conv_bn.running_var", i), d);
GET_LN (b.norm_ff2_w, lname("enc.blocks.%d.norm_ff2.weight", i));
GET_LIN_W(b.ff2_up_w, lname("enc.blocks.%d.ff2_up.weight", i), d, dff);
GET_LIN_W(b.ff2_down_w, lname("enc.blocks.%d.ff2_down.weight", i), dff, d);
GET_LN (b.norm_post_w, lname("enc.blocks.%d.norm_post.weight", i));
return TRANSCRIBE_OK;
}
}
transcribe_status build_medasr_weights(ggml_context * ctx_meta,
const MedAsrHParams & hp,
MedAsrWeights & weights)
{
if (ctx_meta == nullptr) return TRANSCRIBE_ERR_INVALID_ARG;
const int d = hp.enc_hidden;
const int n_mels = hp.enc_num_mel_bins;
const int sub_k = hp.enc_sub_kernel;
const int sub_c = hp.enc_sub_channels;
const int n_freq = hp.fe_n_fft / 2 + 1;
GET_F32_2D(weights.frontend_mel_filterbank,
"frontend.mel_filterbank", hp.fe_num_mels, n_freq);
GET_F32_1D(weights.frontend_window, "frontend.window", hp.fe_win_length);
GET_LIN_W(weights.subsampling.dense0_w, "enc.subsampling.dense_0.weight", n_mels, d);
GET_LIN_B(weights.subsampling.dense0_b, "enc.subsampling.dense_0.bias", d);
GET_CONV_W(weights.subsampling.conv0_w, "enc.subsampling.conv_0.weight", sub_k, d, d);
GET_LIN_B (weights.subsampling.conv0_b, "enc.subsampling.conv_0.bias", d);
GET_CONV_W(weights.subsampling.conv1_w, "enc.subsampling.conv_1.weight", sub_k, d, sub_c);
GET_LIN_B (weights.subsampling.conv1_b, "enc.subsampling.conv_1.bias", sub_c);
GET_LIN_W (weights.subsampling.dense1_w,"enc.subsampling.dense_1.weight", sub_c, d);
GET_LIN_B (weights.subsampling.dense1_b,"enc.subsampling.dense_1.bias", d);
weights.blocks.resize(hp.enc_n_layers);
g_raw_bn.assign(hp.enc_n_layers, RawBnTensors{});
for (int i = 0; i < hp.enc_n_layers; ++i) {
if (auto st = build_block(ctx_meta, hp, i, weights.blocks[i], g_raw_bn[i]);
st != TRANSCRIBE_OK) return st;
}
GET_LN(weights.enc_out_norm_w, "enc.out_norm.weight");
GET_CONV_W(weights.ctc_head.proj_w, "ctc.proj.weight", 1, d, hp.ctc_vocab_size);
GET_LIN_B (weights.ctc_head.proj_b, "ctc.proj.bias", hp.ctc_vocab_size);
return TRANSCRIBE_OK;
}
transcribe_status fuse_batch_norm(const gguf_context * ,
ggml_context * ,
const MedAsrHParams & hp,
MedAsrWeights & weights)
{
if (hp.enc_n_layers <= 0 || hp.enc_hidden <= 0) {
return TRANSCRIBE_ERR_INVALID_ARG;
}
if (static_cast<int>(g_raw_bn.size()) != hp.enc_n_layers) {
log_msg(TRANSCRIBE_LOG_LEVEL_ERROR,
"medasr fuse_batch_norm: raw BN catalog size %zu != "
"n_layers %d",
g_raw_bn.size(), hp.enc_n_layers);
return TRANSCRIBE_ERR_GGUF;
}
constexpr float kBnEps = 1e-5f;
const int d = hp.enc_hidden;
weights.fused_bn_scale_storage.assign(static_cast<size_t>(hp.enc_n_layers),
std::vector<float>(d, 0.0f));
weights.fused_bn_bias_storage.assign(static_cast<size_t>(hp.enc_n_layers),
std::vector<float>(d, 0.0f));
std::vector<float> bn_w(d), bn_b(d), bn_mean(d), bn_var(d);
for (int i = 0; i < hp.enc_n_layers; ++i) {
const RawBnTensors & raw = g_raw_bn[static_cast<size_t>(i)];
if (raw.w == nullptr || raw.b == nullptr ||
raw.mean == nullptr || raw.var == nullptr) {
log_msg(TRANSCRIBE_LOG_LEVEL_ERROR,
"medasr fuse_batch_norm: missing raw BN tensor at "
"layer %d", i);
return TRANSCRIBE_ERR_GGUF;
}
ggml_backend_tensor_get(raw.w, bn_w.data(), 0, d * sizeof(float));
ggml_backend_tensor_get(raw.b, bn_b.data(), 0, d * sizeof(float));
ggml_backend_tensor_get(raw.mean, bn_mean.data(), 0, d * sizeof(float));
ggml_backend_tensor_get(raw.var, bn_var.data(), 0, d * sizeof(float));
auto & scale = weights.fused_bn_scale_storage[static_cast<size_t>(i)];
auto & bias = weights.fused_bn_bias_storage [static_cast<size_t>(i)];
for (int c = 0; c < d; ++c) {
const float s = bn_w[c] / std::sqrt(bn_var[c] + kBnEps);
scale[c] = s;
bias[c] = bn_b[c] - bn_mean[c] * s;
}
}
g_raw_bn.clear();
return TRANSCRIBE_OK;
}
}