#include "projector.h"
#include "granite.h"
#include "weights.h"
#include "transcribe-debug.h"
#include "transcribe-log.h"
#include "ggml.h"
#include <cmath>
#include <cstdio>
#include <cstring>
namespace transcribe::granite {
namespace {
ggml_tensor * named(ggml_tensor * t, const char * name) {
if (t != nullptr && name != nullptr) {
ggml_set_name(t, name);
}
return t;
}
ggml_tensor * qformer_layer_norm(ggml_context * ctx,
ggml_tensor * x,
ggml_tensor * gamma,
ggml_tensor * beta,
float eps)
{
ggml_tensor * y = ggml_norm(ctx, x, eps);
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 * qformer_attn(ggml_context * ctx,
ggml_tensor * q_in, ggml_tensor * k_in, ggml_tensor * v_in, int n_heads,
int head_dim)
{
const int64_t hidden = q_in->ne[0];
const int64_t q_seq = q_in->ne[1];
const int64_t k_seq = k_in->ne[1];
const int64_t batch = q_in->ne[2];
const float scale = 1.0f / std::sqrt(static_cast<float>(head_dim));
(void)hidden;
auto split_heads = [&](ggml_tensor * t, int64_t seq) {
ggml_tensor * r = ggml_reshape_4d(ctx, t,
head_dim, n_heads, seq, batch);
r = ggml_cont(ctx, ggml_permute(ctx, r, 0, 2, 1, 3));
return r;
};
ggml_tensor * q = split_heads(q_in, q_seq);
ggml_tensor * k = split_heads(k_in, k_seq);
ggml_tensor * v = split_heads(v_in, k_seq);
ggml_tensor * kq = ggml_mul_mat(ctx, k, q);
kq = ggml_scale(ctx, kq, scale);
ggml_tensor * attn = ggml_soft_max(ctx, kq);
ggml_tensor * v_t = ggml_cont(ctx, ggml_permute(ctx, v, 1, 0, 2, 3));
ggml_tensor * out = ggml_mul_mat(ctx, v_t, attn);
out = ggml_cont(ctx, ggml_permute(ctx, out, 0, 2, 1, 3));
out = ggml_reshape_3d(ctx, out,
static_cast<int64_t>(head_dim) * n_heads,
q_seq, batch);
return out;
}
ggml_tensor * qformer_layer(ggml_context * ctx,
ggml_tensor * query, ggml_tensor * enc_window, const GraniteProjBlock & b,
int n_heads,
int head_dim,
float ln_eps)
{
ggml_tensor * sa_in = query;
ggml_tensor * sa_q = linear(ctx, sa_in, b.self_attn_q_w, b.self_attn_q_b);
ggml_tensor * sa_k = linear(ctx, sa_in, b.self_attn_k_w, b.self_attn_k_b);
ggml_tensor * sa_v = linear(ctx, sa_in, b.self_attn_v_w, b.self_attn_v_b);
ggml_tensor * sa = qformer_attn(ctx, sa_q, sa_k, sa_v, n_heads, head_dim);
sa = linear(ctx, sa, b.self_attn_out_w, b.self_attn_out_b);
sa = ggml_add(ctx, sa, sa_in);
sa = qformer_layer_norm(ctx, sa, b.norm_self_attn_w, b.norm_self_attn_b, ln_eps);
ggml_tensor * ca_in = sa;
ggml_tensor * ca_q = linear(ctx, ca_in, b.cross_attn_q_w, b.cross_attn_q_b);
ggml_tensor * ca_k = linear(ctx, enc_window, b.cross_attn_k_w, b.cross_attn_k_b);
ggml_tensor * ca_v = linear(ctx, enc_window, b.cross_attn_v_w, b.cross_attn_v_b);
ggml_tensor * ca = qformer_attn(ctx, ca_q, ca_k, ca_v, n_heads, head_dim);
ca = linear(ctx, ca, b.cross_attn_out_w, b.cross_attn_out_b);
ca = ggml_add(ctx, ca, ca_in);
ca = qformer_layer_norm(ctx, ca, b.norm_cross_attn_w, b.norm_cross_attn_b, ln_eps);
ggml_tensor * ffn_in = ca;
ggml_tensor * mid = linear(ctx, ffn_in, b.ffn_up_w, b.ffn_up_b);
mid = ggml_gelu_erf(ctx, mid);
ggml_tensor * ffn_out = linear(ctx, mid, b.ffn_down_w, b.ffn_down_b);
ffn_out = ggml_add(ctx, ffn_out, ffn_in);
ffn_out = qformer_layer_norm(ctx, ffn_out, b.norm_ffn_w, b.norm_ffn_b, ln_eps);
return ffn_out;
}
}
ProjectorBuild build_projector_graph(ggml_context * ctx,
const GraniteWeights & weights,
const GraniteHParams & hp,
int T_enc)
{
ProjectorBuild pb {};
const int window_size = hp.window_size; const int downsample = hp.downsample_rate; const int num_queries = window_size / downsample; const int n_heads = hp.prj_n_heads; const int prj_hidden = hp.prj_hidden; const int enc_hidden = hp.prj_encoder_hidden_size; (void)hp.dec_hidden; const float ln_eps = hp.prj_layer_norm_eps;
if (prj_hidden % n_heads != 0) {
log_msg(TRANSCRIBE_LOG_LEVEL_ERROR,
"granite projector: prj_hidden (%d) not divisible by "
"n_heads (%d)", prj_hidden, n_heads);
return pb;
}
const int head_dim = prj_hidden / n_heads;
pb.nblocks = (T_enc + window_size - 1) / window_size;
const int T_pad = pb.nblocks * window_size;
pb.t_enc_pad = T_pad - T_enc;
pb.n_audio_tokens = pb.nblocks * num_queries;
pb.enc_in = ggml_new_tensor_2d(ctx, GGML_TYPE_F32, enc_hidden, T_enc);
named(pb.enc_in, "proj.enc_in");
ggml_set_input(pb.enc_in);
if (pb.t_enc_pad > 0) {
pb.enc_pad = ggml_new_tensor_2d(ctx, GGML_TYPE_F32,
enc_hidden, pb.t_enc_pad);
named(pb.enc_pad, "proj.enc_pad");
ggml_set_input(pb.enc_pad);
}
ggml_tensor * enc_full = pb.enc_in;
if (pb.enc_pad != nullptr) {
enc_full = ggml_concat(ctx, pb.enc_in, pb.enc_pad, 1);
}
ggml_tensor * enc_window = ggml_reshape_3d(ctx, enc_full,
enc_hidden, window_size,
pb.nblocks);
ggml_tensor * query = ggml_cast(ctx, weights.proj_top.query, GGML_TYPE_F32);
query = qformer_layer_norm(ctx, query,
weights.proj_top.qformer_final_norm_w,
weights.proj_top.qformer_final_norm_b,
ln_eps);
query = ggml_repeat_4d(ctx, query,
prj_hidden, num_queries, pb.nblocks, 1);
for (int i = 0; i < hp.prj_n_layers; ++i) {
query = qformer_layer(ctx, query, enc_window,
weights.proj_blocks[i],
n_heads, head_dim, ln_eps);
}
named(query, "proj.qformer.out");
pb.dumps.qformer_out = query;
transcribe::debug::mark_tensor_for_dump(query);
ggml_tensor * tokens = ggml_reshape_2d(ctx, query,
prj_hidden,
static_cast<int64_t>(num_queries) * pb.nblocks);
ggml_tensor * out = ggml_mul_mat(ctx, weights.proj_top.linear_w, tokens);
out = ggml_add(ctx, out, weights.proj_top.linear_b);
named(out, "proj.out");
pb.out = out;
pb.dumps.proj_out = out;
ggml_set_output(out);
transcribe::debug::mark_tensor_for_dump(out);
pb.graph = ggml_new_graph_custom(ctx, 4096, false);
if (pb.graph == nullptr) {
log_msg(TRANSCRIBE_LOG_LEVEL_ERROR, "granite projector: ggml_new_graph_custom failed");
return pb;
}
ggml_build_forward_expand(pb.graph, pb.out);
if (pb.dumps.qformer_out) {
ggml_build_forward_expand(pb.graph, pb.dumps.qformer_out);
}
return pb;
}
}