#include "weights.h"
#include "ggml.h"
#include "gguf.h"
#include "transcribe-log.h"
#include "transcribe-meta.h"
#include "transcribe-weights-util.h"
#include <algorithm>
#include <cstdio>
namespace transcribe::granite5_ctc {
namespace {
constexpr const char * kFamilyTag = "granite5_ctc";
}
transcribe_status read_granite5_ctc_hparams(const gguf_context * gguf, Granite5CtcHParams & hp) {
if (gguf == nullptr) {
return TRANSCRIBE_ERR_INVALID_ARG;
}
if (auto st = read_optional_string_kv(gguf, "stt.variant", kFamilyTag, "", hp.variant); st != TRANSCRIBE_OK) {
return st;
}
struct U32Field {
const char * key;
int32_t * slot;
};
const U32Field enc_fields[] = {
{ "stt.granite5_ctc.encoder.n_layers", &hp.enc_n_layers },
{ "stt.granite5_ctc.encoder.hidden", &hp.enc_hidden },
{ "stt.granite5_ctc.encoder.n_heads", &hp.enc_n_heads },
{ "stt.granite5_ctc.encoder.head_dim", &hp.enc_head_dim },
{ "stt.granite5_ctc.encoder.intermediate", &hp.enc_intermediate },
{ "stt.granite5_ctc.encoder.input_dim", &hp.enc_input_dim },
{ "stt.granite5_ctc.encoder.output_dim", &hp.enc_output_dim },
{ "stt.granite5_ctc.encoder.conv_kernel_size", &hp.enc_conv_kernel_size },
{ "stt.granite5_ctc.encoder.conv_expansion", &hp.enc_conv_expansion },
{ "stt.granite5_ctc.encoder.context_size", &hp.enc_context_size },
{ "stt.granite5_ctc.encoder.max_pos_emb", &hp.enc_max_pos_emb },
{ "stt.granite5_ctc.encoder.self_cond_layer", &hp.enc_self_cond_layer },
};
for (const auto & f : enc_fields) {
if (auto st = read_required_u32_kv(gguf, f.key, kFamilyTag, *f.slot); st != TRANSCRIBE_OK) {
return st;
}
}
{
std::vector<int32_t> tmp;
switch (read_int32_array_kv(gguf, "stt.granite5_ctc.encoder.subsample_layers", tmp)) {
case KvResult::Ok:
hp.enc_subsample_layers = std::move(tmp);
break;
case KvResult::Absent:
hp.enc_subsample_layers.clear();
break;
case KvResult::BadType:
log_msg(TRANSCRIBE_LOG_LEVEL_ERROR,
"granite5_ctc: stt.granite5_ctc.encoder.subsample_layers has wrong type");
return TRANSCRIBE_ERR_GGUF;
}
}
std::sort(hp.enc_subsample_layers.begin(), hp.enc_subsample_layers.end());
for (int32_t idx : hp.enc_subsample_layers) {
if (idx < 0 || idx >= hp.enc_n_layers) {
log_msg(TRANSCRIBE_LOG_LEVEL_ERROR, "granite5_ctc: subsample_layers entry %d out of range [0, %d)", idx,
hp.enc_n_layers);
return TRANSCRIBE_ERR_GGUF;
}
}
if (auto st = read_required_u32_kv(gguf, "stt.granite5_ctc.blank_id", kFamilyTag, hp.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;
}
const U32Field fe_fields[] = {
{ "stt.frontend.sample_rate", &hp.fe_sample_rate },
{ "stt.frontend.num_mels", &hp.fe_num_mels },
{ "stt.frontend.n_fft", &hp.fe_n_fft },
{ "stt.frontend.win_length", &hp.fe_win_length },
{ "stt.frontend.hop_length", &hp.fe_hop_length },
{ "stt.frontend.delta_win_length", &hp.fe_delta_win_length },
{ "stt.frontend.stack_factor", &hp.fe_stack_factor },
};
for (const auto & f : fe_fields) {
if (auto st = read_required_u32_kv(gguf, f.key, kFamilyTag, *f.slot); 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_optional_string_kv(gguf, "stt.frontend.pad_mode", kFamilyTag, "reflect", hp.fe_pad_mode);
st != TRANSCRIBE_OK) {
return st;
}
if (auto st = read_optional_string_kv(gguf, "stt.frontend.mel_norm", kFamilyTag, "htk", hp.fe_mel_norm);
st != TRANSCRIBE_OK) {
return st;
}
if (auto st = read_required_f32_kv(gguf, "stt.frontend.dither", kFamilyTag, hp.fe_dither); st != TRANSCRIBE_OK) {
return st;
}
if (auto st = read_required_f32_kv(gguf, "stt.frontend.logmel_floor_db", kFamilyTag, hp.fe_logmel_floor_db);
st != TRANSCRIBE_OK) {
return st;
}
if (auto st = read_optional_bool_kv(gguf, "stt.frontend.deltas", kFamilyTag, true, hp.fe_deltas);
st != TRANSCRIBE_OK) {
return st;
}
if (hp.fe_normalize != "per_utterance") {
log_msg(TRANSCRIBE_LOG_LEVEL_ERROR,
"granite5_ctc: unsupported frontend normalize \"%s\" (only \"per_utterance\")",
hp.fe_normalize.c_str());
return TRANSCRIBE_ERR_GGUF;
}
if (hp.fe_window != "hann_periodic") {
log_msg(TRANSCRIBE_LOG_LEVEL_ERROR, "granite5_ctc: unsupported frontend window \"%s\" (only \"hann_periodic\")",
hp.fe_window.c_str());
return TRANSCRIBE_ERR_GGUF;
}
if (hp.fe_mel_norm != "htk") {
log_msg(TRANSCRIBE_LOG_LEVEL_ERROR, "granite5_ctc: unsupported frontend mel_norm \"%s\" (only \"htk\")",
hp.fe_mel_norm.c_str());
return TRANSCRIBE_ERR_GGUF;
}
if (hp.fe_stack_factor != 2) {
log_msg(TRANSCRIBE_LOG_LEVEL_ERROR, "granite5_ctc: unsupported frontend stack_factor %d (only 2)",
hp.fe_stack_factor);
return TRANSCRIBE_ERR_GGUF;
}
if (hp.fe_delta_win_length != 3) {
log_msg(TRANSCRIBE_LOG_LEVEL_ERROR,
"granite5_ctc: unsupported frontend delta_win_length %d (only 3; the "
"host delta kernel is the win_length=3 special case)",
hp.fe_delta_win_length);
return TRANSCRIBE_ERR_GGUF;
}
{
const int32_t mel_ch = hp.fe_num_mels * (hp.fe_deltas ? 2 : 1);
const int32_t expected = mel_ch * hp.fe_stack_factor;
if (expected != hp.enc_input_dim) {
log_msg(TRANSCRIBE_LOG_LEVEL_ERROR,
"granite5_ctc: frontend produces %d features "
"(%d mel x %d delta x %d stack) but encoder.input_dim is %d",
expected, hp.fe_num_mels, hp.fe_deltas ? 2 : 1, hp.fe_stack_factor, hp.enc_input_dim);
return TRANSCRIBE_ERR_GGUF;
}
}
if ((hp.enc_conv_kernel_size % 2) == 0) {
log_msg(TRANSCRIBE_LOG_LEVEL_ERROR,
"granite5_ctc: conv_kernel_size %d is even; SAME padding requires an odd kernel",
hp.enc_conv_kernel_size);
return TRANSCRIBE_ERR_GGUF;
}
if (hp.enc_context_size <= 0) {
log_msg(TRANSCRIBE_LOG_LEVEL_ERROR, "granite5_ctc: context_size must be positive, got %d", hp.enc_context_size);
return TRANSCRIBE_ERR_GGUF;
}
if (hp.enc_self_cond_layer < 0 || hp.enc_self_cond_layer > hp.enc_n_layers) {
log_msg(TRANSCRIBE_LOG_LEVEL_ERROR, "granite5_ctc: self_cond_layer %d out of range [0, %d]",
hp.enc_self_cond_layer, hp.enc_n_layers);
return TRANSCRIBE_ERR_GGUF;
}
return TRANSCRIBE_OK;
}
namespace {
using transcribe::weights::lname;
constexpr const char * kTag = kFamilyTag;
#define GET_F32(slot, name, ...) \
do { \
ggml_tensor * _t = \
transcribe::weights::find_tensor(ctx_meta, (name), { GGML_TYPE_F32 }, { __VA_ARGS__ }, kTag); \
if (_t == nullptr) \
return TRANSCRIBE_ERR_GGUF; \
(slot) = _t; \
} while (0)
#define GET_CONV(slot, name, ...) \
do { \
ggml_tensor * _t = transcribe::weights::find_tensor(ctx_meta, (name), { TRANSCRIBE_QUANT_CONV_TYPES }, \
{ __VA_ARGS__ }, kTag); \
if (_t == nullptr) \
return TRANSCRIBE_ERR_GGUF; \
(slot) = _t; \
} while (0)
#define GET_LIN(slot, name, ...) \
do { \
ggml_tensor * _t = transcribe::weights::find_tensor(ctx_meta, (name), { TRANSCRIBE_QUANT_LINEAR_TYPES }, \
{ __VA_ARGS__ }, kTag); \
if (_t == nullptr) \
return TRANSCRIBE_ERR_GGUF; \
(slot) = _t; \
} while (0)
}
transcribe_status build_granite5_ctc_weights(ggml_context * ctx_meta,
const Granite5CtcHParams & hp,
Granite5CtcWeights & weights) {
if (ctx_meta == nullptr) {
return TRANSCRIBE_ERR_INVALID_ARG;
}
const int64_t enc_h = hp.enc_hidden;
const int64_t enc_in = hp.enc_input_dim;
const int64_t enc_out = hp.enc_output_dim;
const int64_t enc_inner = static_cast<int64_t>(hp.enc_n_heads) * hp.enc_head_dim;
const int64_t enc_ffn = hp.enc_intermediate;
const int64_t conv_inner = enc_h * hp.enc_conv_expansion;
const int64_t conv_up_out = conv_inner * 2; const int64_t conv_k = hp.enc_conv_kernel_size;
const int64_t head_dim = hp.enc_head_dim;
const int64_t rel_pos_len = 2LL * hp.enc_max_pos_emb + 1;
GET_LIN(weights.enc_top.input_linear_w, "enc.input_linear.weight", enc_in, enc_h);
GET_F32(weights.enc_top.input_linear_b, "enc.input_linear.bias", enc_h);
GET_LIN(weights.enc_top.ctc_proj_w, "enc.ctc_proj.weight", enc_h, enc_out);
GET_F32(weights.enc_top.ctc_proj_b, "enc.ctc_proj.bias", enc_out);
GET_LIN(weights.enc_top.ctc_bypass_w, "enc.ctc_bypass.weight", enc_out, enc_h);
GET_F32(weights.enc_top.ctc_bypass_b, "enc.ctc_bypass.bias", enc_h);
weights.enc_blocks.assign(hp.enc_n_layers, Granite5CtcEncBlock{});
for (int i = 0; i < hp.enc_n_layers; ++i) {
auto & b = weights.enc_blocks[i];
GET_F32(b.norm_ff1_w, lname("enc.blocks.%d.norm_ff1.weight", i), enc_h);
GET_F32(b.norm_ff1_b, lname("enc.blocks.%d.norm_ff1.bias", i), enc_h);
GET_LIN(b.ff1_lin1_w, lname("enc.blocks.%d.ff1.linear1.weight", i), enc_h, enc_ffn);
GET_F32(b.ff1_lin1_b, lname("enc.blocks.%d.ff1.linear1.bias", i), enc_ffn);
GET_LIN(b.ff1_lin2_w, lname("enc.blocks.%d.ff1.linear2.weight", i), enc_ffn, enc_h);
GET_F32(b.ff1_lin2_b, lname("enc.blocks.%d.ff1.linear2.bias", i), enc_h);
GET_F32(b.norm_attn_w, lname("enc.blocks.%d.norm_attn.weight", i), enc_h);
GET_F32(b.norm_attn_b, lname("enc.blocks.%d.norm_attn.bias", i), enc_h);
GET_LIN(b.attn_q_w, lname("enc.blocks.%d.attn.q.weight", i), enc_h, enc_inner);
GET_LIN(b.attn_kv_w, lname("enc.blocks.%d.attn.kv.weight", i), enc_h, 2 * enc_inner);
GET_LIN(b.attn_out_w, lname("enc.blocks.%d.attn.out.weight", i), enc_inner, enc_h);
GET_F32(b.attn_out_b, lname("enc.blocks.%d.attn.out.bias", i), enc_h);
GET_LIN(b.attn_rel_pos_emb, lname("enc.blocks.%d.attn.rel_pos_emb.weight", i), head_dim, rel_pos_len);
GET_F32(b.norm_conv_w, lname("enc.blocks.%d.norm_conv.weight", i), enc_h);
GET_F32(b.norm_conv_b, lname("enc.blocks.%d.norm_conv.bias", i), enc_h);
GET_LIN(b.conv_pointwise1_w, lname("enc.blocks.%d.conv.pointwise1.weight", i), enc_h, conv_up_out);
GET_F32(b.conv_pointwise1_b, lname("enc.blocks.%d.conv.pointwise1.bias", i), conv_up_out);
GET_CONV(b.conv_depthwise_w, lname("enc.blocks.%d.conv.depthwise.weight", i), conv_k, 1, conv_inner);
GET_F32(b.conv_bn_w, lname("enc.blocks.%d.conv.bn.weight", i), conv_inner);
GET_F32(b.conv_bn_b, lname("enc.blocks.%d.conv.bn.bias", i), conv_inner);
GET_F32(b.conv_bn_mean, lname("enc.blocks.%d.conv.bn.running_mean", i), conv_inner);
GET_F32(b.conv_bn_var, lname("enc.blocks.%d.conv.bn.running_var", i), conv_inner);
GET_LIN(b.conv_pointwise2_w, lname("enc.blocks.%d.conv.pointwise2.weight", i), conv_inner, enc_h);
GET_F32(b.conv_pointwise2_b, lname("enc.blocks.%d.conv.pointwise2.bias", i), enc_h);
GET_F32(b.norm_ff2_w, lname("enc.blocks.%d.norm_ff2.weight", i), enc_h);
GET_F32(b.norm_ff2_b, lname("enc.blocks.%d.norm_ff2.bias", i), enc_h);
GET_LIN(b.ff2_lin1_w, lname("enc.blocks.%d.ff2.linear1.weight", i), enc_h, enc_ffn);
GET_F32(b.ff2_lin1_b, lname("enc.blocks.%d.ff2.linear1.bias", i), enc_ffn);
GET_LIN(b.ff2_lin2_w, lname("enc.blocks.%d.ff2.linear2.weight", i), enc_ffn, enc_h);
GET_F32(b.ff2_lin2_b, lname("enc.blocks.%d.ff2.linear2.bias", i), enc_h);
GET_F32(b.norm_out_w, lname("enc.blocks.%d.norm_out.weight", i), enc_h);
GET_F32(b.norm_out_b, lname("enc.blocks.%d.norm_out.bias", i), enc_h);
}
const int64_t n_freq = static_cast<int64_t>(hp.fe_n_fft) / 2 + 1;
GET_F32(weights.frontend_mel_filterbank, "frontend.mel_filterbank", n_freq, hp.fe_num_mels);
GET_F32(weights.frontend_window, "frontend.window", hp.fe_win_length);
return TRANSCRIBE_OK;
}
}