Files
llama.cpp/tools/mtmd/models/qwen3tts-gen.cpp
T
Xuan-Son Nguyen 6e62ba5384 mtmd: support pocket-tts (#26871)
* adapt the api

* text model ok

* working impl, need verify and clean up

* mtmd: build the pocket-tts transposed convolutions as GEMM + col2im

ggml_conv_transpose_1d has no grouped mode, so the depthwise upsample
was built as one convolution and one concat per channel, which floods
the graph with small nodes and makes kernel launches dominate the
decoder.

Fold both cases into the column form the seanet decoder already needs:
the general case reshapes the kernel to [IC, K * OC] and matmuls it
with the input, the depthwise case batches a matmul over the channels
so a step scales its own kernel. A single col2im_1d then scatter-adds
the columns back to the signal, with the same shape as before, so the
overlap-add tail, the streaming state and the bias are untouched.

Generation time per frame drops by 80% on CUDA and by 50% on CPU. The
output matches the previous implementation sample for sample, with a
correlation of 0.999994 and identical frame counts.

* flow_temp +  frames_after_eos

* chunking

* mtmd: carry the remaining pocket-tts per-pack settings

The language packs also tune the end-of-speech padding and the padding
of short prompts, next to the temperature already carried in the
mmproj: french_24l asks for 8 tail frames instead of the guessed 3,
english_2026-01 asks for short prompts to be padded with spaces.

Write both in the mmproj as clip.gen.audio.frames_after_eos and
clip.gen.audio.pad_short_text, keyed on the pack in the conversion
script like the temperature. The loader keeps them optional, so a
mmproj without them behaves as before. Map semicolons to commas for
every pack instead, the reference only asks for it on three of them and
it costs nothing elsewhere.

Existing mmproj files must be converted again to carry the two keys.

On a long french text the port now lands within 2% of the reference:
22.96s against 23.44s, with the same peak level and the same amount of
silence.

* clip.gen.audio.model_variant

* clean up code comments

* nit: drop the dead flow_temp hparam, the pack table holds the default

* update docs

* address security problems

* less invasive base.py

* lint

* add mtmd_gen_inp_default

* add docs

* rm gen_flow_temp

---------

Co-authored-by: Pascal <admin@serveurperso.com>
2026-08-11 14:18:30 +02:00

771 lines
36 KiB
C++

#include "models.h"
#include <string>
// on-device sampling: top-k, top-p, then a random draw
ggml_tensor * clip_graph_qwen3tts_gen::code_gen::do_sampling(ggml_tensor * logits, ggml_tensor * inp_rand) const {
logits = ggml_reshape_1d(ctx0, logits, ggml_nelements(logits));
const int64_t n_vocab = logits->ne[0];
// sort a's rows by idx
auto sort_by = [this](ggml_tensor * a, ggml_tensor * idx) {
ggml_tensor * a2d = ggml_reshape_2d(ctx0, a, 1, a->ne[0]);
return ggml_reshape_1d(ctx0, ggml_get_rows(ctx0, a2d, idx), idx->ne[0]);
};
ggml_tensor * cur = logits;
ggml_tensor * candidates = nullptr; // maps row index back to vocab id
if (top_k > 0 && top_k < n_vocab) {
ggml_tensor * idx = ggml_top_k(ctx0, cur, top_k);
candidates = idx;
cur = sort_by(cur, idx);
cb(cur, "sample_top_k_logits", -1);
}
if (top_p < 1.0f) {
ggml_tensor * sorted_idx = ggml_argsort(ctx0, cur, GGML_SORT_ORDER_DESC);
ggml_tensor * sorted_logits = sort_by(cur, sorted_idx);
candidates = candidates ? sort_by(candidates, sorted_idx) : sorted_idx;
ggml_tensor * probs = ggml_soft_max(ctx0, sorted_logits);
ggml_tensor * cdf = ggml_cumsum(ctx0, probs);
// keep_mask[i] = 1 once cdf[i] crosses top_p
ggml_tensor * cdf_scaled = ggml_scale_bias(ctx0, cdf, -1.0f, top_p);
ggml_tensor * keep_mask = ggml_step(ctx0, cdf_scaled);
ggml_tensor * idxf = ggml_sum(ctx0, keep_mask);
idxf = ggml_clamp(ctx0, idxf, 0.0f, (float) keep_mask->ne[0] - 1);
ggml_tensor * ones = ggml_scale_bias(ctx0, idxf, 0.0f, 1.0f);
// top-p must include the crossing element, so force it to 1
ggml_tensor * keep_mask_2d = ggml_reshape_2d(ctx0, keep_mask, 1, keep_mask->ne[0]);
keep_mask_2d = ggml_set_rows(ctx0, keep_mask_2d, ones, ggml_cast(ctx0, idxf, GGML_TYPE_I32));
keep_mask = ggml_reshape_1d(ctx0, keep_mask_2d, keep_mask->ne[0]);
// log(1) = 0 (keep), log(0) = -inf (drop)
ggml_tensor * bias = ggml_log(ctx0, keep_mask);
cur = ggml_add(ctx0, sorted_logits, bias);
cb(cur, "sample_top_p_logits", -1);
}
// draw one token: find where the cdf crosses inp_rand
ggml_tensor * probs = ggml_soft_max(ctx0, cur);
ggml_tensor * cumsum = ggml_cumsum(ctx0, probs);
ggml_tensor * diff = ggml_sub(ctx0, cumsum, inp_rand);
ggml_tensor * cross_mask = ggml_step(ctx0, diff);
ggml_tensor * idxf = ggml_sum(ctx0, cross_mask);
ggml_tensor * idx = ggml_cast(ctx0, ggml_scale_bias(ctx0, idxf, -1.0f, (float) cross_mask->ne[0]), GGML_TYPE_I32);
if (candidates) {
ggml_tensor * cand_2d = ggml_reshape_2d(ctx0, candidates, 1, candidates->ne[0]);
idx = ggml_get_rows(ctx0, cand_2d, idx);
}
cb(idx, "sample_token_id", -1);
return idx;
}
// returns a new cache with row row_idx set to value
ggml_tensor * clip_graph_qwen3tts_gen::code_gen::cache_set(ggml_tensor * cache, int row_idx, ggml_tensor * value) const {
const int64_t n_embd = cache->ne[0];
const int64_t n_cache = cache->ne[1];
GGML_ASSERT(row_idx >= 0 && row_idx < n_cache);
// append value as the last row, then gather it back into place
ggml_tensor * value_2d = ggml_reshape_2d(ctx0, value, n_embd, 1);
ggml_tensor * cache_ext = ggml_concat(ctx0, cache, value_2d, 1); // [n_embd, n_cache + 1]
// gather indices [0..row_idx-1, n_cache, row_idx+1..n_cache-1]
// built via concat, since ggml_set_rows needs F32/F16 values, not an I32 index array
ggml_tensor * idx = const_i32(cache, (float) n_cache);
if (row_idx > 0) {
ggml_tensor * prefix = ggml_cast(ctx0, ggml_arange(ctx0, 0.0f, (float) row_idx, 1.0f), GGML_TYPE_I32);
idx = ggml_concat(ctx0, prefix, idx, 0);
}
if (row_idx < n_cache - 1) {
ggml_tensor * suffix = ggml_cast(ctx0, ggml_arange(ctx0, (float) (row_idx + 1), (float) n_cache, 1.0f), GGML_TYPE_I32);
idx = ggml_concat(ctx0, idx, suffix, 0);
}
ggml_tensor * result = ggml_get_rows(ctx0, cache_ext, idx);
cb(result, "cache_set_out", -1);
return result;
}
// builds a const i32 with no host upload: view a tensor, zero it via scale, add value, cast to i32
ggml_tensor * clip_graph_qwen3tts_gen::code_gen::const_i32(ggml_tensor * anchor, float value) const {
ggml_tensor * v = ggml_view_1d(ctx0, anchor, 1, 0);
if (v->type != GGML_TYPE_F32) {
v = ggml_cast(ctx0, v, GGML_TYPE_F32);
}
return ggml_cast(ctx0, ggml_scale_bias(ctx0, v, 0.0f, value), GGML_TYPE_I32);
}
// causal keep-mask row for a query at position pos, window size n_kv_pad
ggml_tensor * clip_graph_qwen3tts_gen::code_gen::causal_mask_row(int64_t n_kv_pad, int pos) const {
ggml_tensor * ones = ggml_fill(ctx0, ggml_new_tensor_2d(ctx0, GGML_TYPE_F32, n_kv_pad, n_kv_pad), 1.0f);
ggml_tensor * keep = ggml_tri(ctx0, ones, GGML_TRI_TYPE_LOWER_DIAG);
ggml_tensor * row = ggml_view_1d(ctx0, keep, n_kv_pad, (size_t) pos * keep->nb[1]);
ggml_tensor * mask = ggml_log(ctx0, row); // 0 = keep, -inf = masked
return ggml_reshape_4d(ctx0, mask, n_kv_pad, 1, 1, 1);
}
// talker hidden size -> predictor hidden size (small_to_mtp_projection)
ggml_tensor * clip_graph_qwen3tts_gen::code_gen::project_in(ggml_tensor * cur) const {
if (!model.gen_code_proj_in_w) {
return cur;
}
cur = ggml_mul_mat(ctx0, model.gen_code_proj_in_w, cur);
if (model.gen_code_proj_in_b) {
cur = ggml_add(ctx0, cur, model.gen_code_proj_in_b);
}
return cur;
}
// one transformer layer at position pos; writes k/v into k_cache_layer/v_cache_layer at row pos
ggml_tensor * clip_graph_qwen3tts_gen::code_gen::layer_forward(
ggml_tensor * cur,
const clip_layer & layer,
ggml_tensor * inp_pos,
ggml_tensor * kq_mask,
ggml_tensor *& k_cache_layer,
ggml_tensor *& v_cache_layer,
int64_t n_kv_pad,
int pos,
int il) const {
const int n_head = hparams.n_head;
const int n_head_kv = hparams.n_head_kv;
const int64_t d_head = layer.q_w->ne[1] / n_head; // real head_dim, not n_embd / n_head
const float kq_scale = 1.0f / sqrtf((float) d_head);
ggml_tensor * residual = cur;
ggml_tensor * h = ggml_rms_norm(ctx0, cur, hparams.eps);
h = ggml_mul(ctx0, h, layer.ln_1_w);
ggml_tensor * q = ggml_mul_mat(ctx0, layer.q_w, h);
ggml_tensor * k = ggml_mul_mat(ctx0, layer.k_w, h);
ggml_tensor * v = ggml_mul_mat(ctx0, layer.v_w, h);
q = ggml_reshape_3d(ctx0, q, d_head, n_head, 1);
k = ggml_reshape_3d(ctx0, k, d_head, n_head_kv, 1);
q = ggml_rms_norm(ctx0, q, hparams.eps);
q = ggml_mul(ctx0, q, layer.q_norm);
k = ggml_rms_norm(ctx0, k, hparams.eps);
k = ggml_mul(ctx0, k, layer.k_norm);
q = ggml_rope_ext(ctx0, q, inp_pos, nullptr, (int) d_head, GGML_ROPE_TYPE_NEOX, 0,
hparams.rope_theta, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f);
k = ggml_rope_ext(ctx0, k, inp_pos, nullptr, (int) d_head, GGML_ROPE_TYPE_NEOX, 0,
hparams.rope_theta, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f);
// write k/v into the cache at row pos, flat layout
ggml_tensor * k_flat = ggml_reshape_1d(ctx0, k, d_head * n_head_kv);
k_cache_layer = cache_set(k_cache_layer, pos, k_flat);
v_cache_layer = cache_set(v_cache_layer, pos, v);
ggml_tensor * q_cur = ggml_reshape_4d(ctx0, q, d_head, n_head, 1, 1);
ggml_tensor * k_cur = ggml_reshape_4d(ctx0, k_cache_layer, d_head, n_head_kv, n_kv_pad, 1);
ggml_tensor * v_cur = ggml_reshape_4d(ctx0, v_cache_layer, d_head, n_head_kv, n_kv_pad, 1);
ggml_tensor * attn_out = build_attn(layer.o_w, layer.o_b, q_cur, k_cur, v_cur, kq_mask, kq_scale, il);
cur = ggml_add(ctx0, residual, attn_out);
ggml_tensor * h2 = ggml_rms_norm(ctx0, cur, hparams.eps);
h2 = ggml_mul(ctx0, h2, layer.ln_2_w);
ggml_tensor * gate = ggml_mul_mat(ctx0, layer.ff_gate_w, h2);
ggml_tensor * up = ggml_mul_mat(ctx0, layer.ff_up_w, h2);
ggml_tensor * gu = ggml_swiglu_split(ctx0, gate, up);
ggml_tensor * down = ggml_mul_mat(ctx0, layer.ff_down_w, gu);
return ggml_add(ctx0, cur, down);
}
// position 0: hidden bridge, seeds the k/v cache, no sampling
// position 1: embed(code0), sample with lm_head[0], write out_code_cache[1]
void clip_graph_qwen3tts_gen::code_gen::prefill(
std::vector<ggml_tensor *> & k_cache,
std::vector<ggml_tensor *> & v_cache,
ggml_tensor *& out_code_cache,
ggml_tensor * h_state,
ggml_tensor * code0_embd,
ggml_tensor * inp_rand) const {
const int64_t n_kv_pad = k_cache[0]->ne[1];
{
ggml_tensor * cur = project_in(h_state);
ggml_tensor * kq_mask = causal_mask_row(n_kv_pad, 0);
ggml_tensor * inp_pos = const_i32(k_cache[0], 0.0f);
for (size_t il = 0; il < model.layers.size(); il++) {
cur = layer_forward(cur, model.layers[il], inp_pos, kq_mask, k_cache[il], v_cache[il], n_kv_pad, 0, (int) il);
}
// position 0's output is unused, it only seeded the cache
}
{
ggml_tensor * cur = project_in(code0_embd);
ggml_tensor * kq_mask = causal_mask_row(n_kv_pad, 1);
ggml_tensor * inp_pos = const_i32(k_cache[0], 1.0f);
for (size_t il = 0; il < model.layers.size(); il++) {
cur = layer_forward(cur, model.layers[il], inp_pos, kq_mask, k_cache[il], v_cache[il], n_kv_pad, 1, (int) il);
}
cur = ggml_rms_norm(ctx0, cur, hparams.eps);
cur = ggml_mul(ctx0, cur, model.gen_code_norm_w);
ggml_tensor * head_w = model.gen_code_head_w;
ggml_tensor * head_g = ggml_view_2d(ctx0, head_w, head_w->ne[0], head_w->ne[1], head_w->nb[1], 0); // lm_head[0]
ggml_tensor * logits = ggml_mul_mat(ctx0, head_g, cur);
ggml_tensor * sampled = do_sampling(logits, inp_rand);
out_code_cache = cache_set(out_code_cache, 1, sampled);
}
}
// one decode step of code_predictor
// at step_idx g:
// - read code from out_code_cache[g], then embed it with codebook table g-1
// - write new kv at cache row g+1, sample with lm_head[g]
// - write result to out_code_cache[g+1]
// step_idx must be in [1, n_acoustic - 1]
ggml_tensor * clip_graph_qwen3tts_gen::code_gen::step(
std::vector<ggml_tensor *> & k_cache,
std::vector<ggml_tensor *> & v_cache,
ggml_tensor * out_code_cache,
ggml_tensor * inp_rand,
int step_idx) const {
const int64_t n_acoustic = model.gen_code_head_w->ne[2];
GGML_ASSERT(step_idx >= 1 && step_idx < n_acoustic);
GGML_ASSERT(k_cache.size() == model.layers.size());
GGML_ASSERT(v_cache.size() == model.layers.size());
const int64_t n_kv_pad = k_cache[0]->ne[1];
const int pos = step_idx + 1; // new cache row and RoPE position
// embed the previous code via this step's codebook table (rows are already scalars)
ggml_tensor * code_in = ggml_view_1d(ctx0, out_code_cache, 1, (size_t) step_idx * out_code_cache->nb[1]);
ggml_tensor * embd_w = model.gen_code_embd_w; // [n_embd_talker, vocab, n_acoustic]
ggml_tensor * embd_g = ggml_view_2d(ctx0, embd_w, embd_w->ne[0], embd_w->ne[1], embd_w->nb[1],
(size_t) (step_idx - 1) * embd_w->nb[2]);
ggml_tensor * cur = ggml_get_rows(ctx0, embd_g, code_in);
cur = ggml_reshape_1d(ctx0, cur, cur->ne[0]);
cb(cur, "step_embd_in", step_idx);
cur = project_in(cur);
cb(cur, "step_proj_in", step_idx);
ggml_tensor * kq_mask = causal_mask_row(n_kv_pad, pos);
ggml_tensor * inp_pos = const_i32(k_cache[0], (float) pos);
for (size_t il = 0; il < model.layers.size(); il++) {
cur = layer_forward(cur, model.layers[il], inp_pos, kq_mask, k_cache[il], v_cache[il], n_kv_pad, pos, (int) il);
cb(cur, "step_layer_out", (int) il);
}
// final norm, this step's lm_head, sample, write the result
cur = ggml_rms_norm(ctx0, cur, hparams.eps);
cur = ggml_mul(ctx0, cur, model.gen_code_norm_w);
ggml_tensor * head_w = model.gen_code_head_w; // [n_embd_pred, vocab, n_acoustic]
ggml_tensor * head_g = ggml_view_2d(ctx0, head_w, head_w->ne[0], head_w->ne[1], head_w->nb[1],
(size_t) step_idx * head_w->nb[2]);
ggml_tensor * logits = ggml_mul_mat(ctx0, head_g, cur);
cb(logits, "step_logits", step_idx);
ggml_tensor * sampled = do_sampling(logits, inp_rand);
cb(sampled, "step_sampled", step_idx);
return cache_set(out_code_cache, pos, sampled);
}
// causal conv1d, stride 1: prepend persisted left-context instead of zero-padding, then a plain conv
// x: [T, IC] (T-first). w: [K, IC, OC]. state_name empty means K == 1 (no left-context). returns [T, OC]
ggml_tensor * clip_graph_qwen3tts_gen::code2wav::causal_conv1d(ggml_tensor * x, ggml_tensor * w, ggml_tensor * b, int dilation, const std::string & state_name) const {
const int K = (int) w->ne[0];
const int pad = (K - 1) * dilation;
ggml_tensor * x_full = x;
if (pad > 0) {
ggml_tensor * left = state_in.at(state_name); // [pad, IC]
x_full = ggml_concat(ctx0, left, x, 0);
}
ggml_tensor * y = ggml_conv_1d(ctx0, w, x_full, 1, 0, dilation); // [T, OC, 1]
y = ggml_reshape_2d(ctx0, y, y->ne[0], y->ne[1]);
if (b) {
y = ggml_add(ctx0, y, ggml_reshape_2d(ctx0, b, 1, b->ne[0]));
}
if (pad > 0) {
ggml_tensor * new_left = ggml_cont(ctx0, ggml_view_2d(ctx0, x_full, pad, x_full->ne[1], x_full->nb[1],
(size_t) (x_full->ne[0] - pad) * x_full->nb[0]));
state_out.push_back({state_name, new_left});
}
return y;
}
// causal depthwise conv1d, stride 1, dilation 1, kernel from w's shape.
// x: [T, C]. w: [K, 1, C]. returns [T, C]. see causal_conv1d for the state contract.
ggml_tensor * clip_graph_qwen3tts_gen::code2wav::causal_conv1d_dw(ggml_tensor * x, ggml_tensor * w, ggml_tensor * b, const std::string & state_name) const {
const int K = (int) w->ne[0];
const int pad = K - 1;
ggml_tensor * x_full = x;
if (pad > 0) {
ggml_tensor * left = state_in.at(state_name); // [pad, C]
x_full = ggml_concat(ctx0, left, x, 0);
}
ggml_tensor * y = ggml_conv_1d_dw(ctx0, w, x_full, 1, 0, 1); // [T, C, 1]
y = ggml_reshape_2d(ctx0, y, y->ne[0], y->ne[1]);
if (b) {
y = ggml_add(ctx0, y, ggml_reshape_2d(ctx0, b, 1, b->ne[0]));
}
if (pad > 0) {
ggml_tensor * new_left = ggml_cont(ctx0, ggml_view_2d(ctx0, x_full, pad, x_full->ne[1], x_full->nb[1],
(size_t) (x_full->ne[0] - pad) * x_full->nb[0]));
state_out.push_back({state_name, new_left});
}
return y;
}
// causal ConvTranspose1d, the (kernel - stride) overlap tail is kept as state for the next call
// x: [T, IC], w: [K, OC, IC]. state_name empty means K == stride (no overlap). returns [T * stride, OC]
ggml_tensor * clip_graph_qwen3tts_gen::code2wav::causal_conv_transpose1d(ggml_tensor * x, ggml_tensor * w, ggml_tensor * b, int stride, const std::string & state_name) const {
const int K = (int) w->ne[0];
const int OC = (int) w->ne[1];
const int trim = K - stride;
const int64_t emit_len = x->ne[0] * stride;
// transposed conv as GEMM + col2im scatter-add, y: [emit_len + trim, OC]
ggml_tensor * w2 = ggml_reshape_2d(ctx0, w, (int64_t) K * OC, w->ne[2]);
w2 = ggml_cont(ctx0, ggml_transpose(ctx0, w2));
ggml_tensor * xt = ggml_cont(ctx0, ggml_transpose(ctx0, x));
ggml_tensor * col = ggml_mul_mat(ctx0, w2, xt);
ggml_tensor * y = ggml_col2im_1d(ctx0, col, stride, OC, 0);
ggml_tensor * out = y;
if (trim > 0) {
ggml_tensor * tail = state_in.at(state_name); // [trim, OC]
ggml_tensor * head = ggml_add(ctx0, ggml_view_2d(ctx0, y, trim, y->ne[1], y->nb[1], 0), tail);
if (emit_len > trim) {
ggml_tensor * middle = ggml_view_2d(ctx0, y, emit_len - trim, y->ne[1], y->nb[1], (size_t) trim * y->nb[0]);
out = ggml_concat(ctx0, head, middle, 0);
} else {
out = head;
}
ggml_tensor * new_tail = ggml_cont(ctx0, ggml_view_2d(ctx0, y, trim, y->ne[1], y->nb[1], (size_t) emit_len * y->nb[0]));
state_out.push_back({state_name, new_tail});
}
if (b) {
out = ggml_add(ctx0, out, ggml_reshape_2d(ctx0, b, 1, b->ne[0]));
}
return out;
}
// SnakeBeta activation: y = x + sin(alpha*x)^2 * inv_beta (alpha/inv_beta folded via exp/reciprocal at conversion time)
// x: [T, C]. alpha/beta: [C], broadcasts over T
ggml_tensor * clip_graph_qwen3tts_gen::code2wav::snake(ggml_tensor * x, ggml_tensor * alpha, ggml_tensor * beta) const {
ggml_tensor * a = ggml_reshape_2d(ctx0, alpha, 1, alpha->ne[0]);
ggml_tensor * b = ggml_reshape_2d(ctx0, beta, 1, beta->ne[0]);
// expand reshapes first so mul/sin/sqr/mul/add lands as consecutive nodes, letting backends fuse them
ggml_build_forward_expand(gf, a);
ggml_build_forward_expand(gf, b);
ggml_tensor * s = ggml_sin(ctx0, ggml_mul(ctx0, x, a));
s = ggml_sqr(ctx0, s);
s = ggml_mul(ctx0, s, b);
return ggml_add(ctx0, x, s);
}
// RVQ codebook decode: T frames of 16 codes -> 512-dim hidden (C-first, [512, T])
// codebook 0 (semantic) and 1..15 (acoustic) sum within their group, project separately, then add
ggml_tensor * clip_graph_qwen3tts_gen::code2wav::quant_decode(ggml_tensor * inp_codes) const {
const auto & c2w = model.c2w;
const int64_t T = inp_codes->ne[0];
// ids for codebook group g over all T frames, [T] I32
auto group_ids = [&](int g) {
return ggml_view_1d(ctx0, inp_codes, T, (size_t) g * inp_codes->nb[1]);
};
ggml_tensor * sem = ggml_get_rows(ctx0, c2w.quant_first_cb_w, group_ids(0)); // [256, T]
ggml_tensor * sem_out = ggml_mul_mat(ctx0, c2w.quant_first_out_w, sem); // [512, T]
ggml_tensor * acc = nullptr;
const int64_t n_acoustic = c2w.quant_rest_cb_w->ne[2];
for (int g = 1; g <= n_acoustic; g++) {
ggml_tensor * cb_g = ggml_view_2d(ctx0, c2w.quant_rest_cb_w, c2w.quant_rest_cb_w->ne[0], c2w.quant_rest_cb_w->ne[1],
c2w.quant_rest_cb_w->nb[1], (size_t) (g - 1) * c2w.quant_rest_cb_w->nb[2]);
ggml_tensor * embd = ggml_get_rows(ctx0, cb_g, group_ids(g)); // [256, T]
acc = acc ? ggml_add(ctx0, acc, embd) : embd;
}
ggml_tensor * ac_out = ggml_mul_mat(ctx0, c2w.quant_rest_out_w, acc); // [512, T]
ggml_tensor * hidden = ggml_add(ctx0, sem_out, ac_out);
cb(hidden, "wav_quant_hidden", -1);
return hidden;
}
// one pre_transformer layer over a batch of N = sliding_window new frames
// attention runs over [(W-1)-frame prefix from the last batch] + [N new frames]
// RoPE positions come from a persisted counter, so phases line up across batches
ggml_tensor * clip_graph_qwen3tts_gen::code2wav::tfm_layer_forward(ggml_tensor * cur, const clip_layer & layer, int il) const {
const int n_head = hparams.wav_tfm_n_head;
const int n_head_kv = hparams.wav_tfm_n_head_kv;
const int64_t d_head = layer.q_w->ne[1] / n_head;
const float kq_scale = 1.0f / sqrtf((float) d_head);
const int64_t W = hparams.wav_tfm_swa; // == N, frames per batch
const int64_t N = cur->ne[1];
const int64_t prefix = W - 1;
const int64_t total_kv = prefix + N;
ggml_tensor * residual = cur;
ggml_tensor * h = ggml_rms_norm(ctx0, cur, hparams.wav_tfm_eps);
h = ggml_mul(ctx0, h, layer.ln_1_w);
ggml_tensor * q = ggml_mul_mat(ctx0, layer.q_w, h); // [n_head*d_head, N]
ggml_tensor * k = ggml_mul_mat(ctx0, layer.k_w, h); // [n_head_kv*d_head, N]
ggml_tensor * v = ggml_mul_mat(ctx0, layer.v_w, h); // [n_head_kv*d_head, N]
q = ggml_reshape_3d(ctx0, q, d_head, n_head, N);
k = ggml_reshape_3d(ctx0, k, d_head, n_head_kv, N);
// real, ever-increasing positions: base (persisted) .. base+N-1
ggml_tensor * base = ggml_reshape_1d(ctx0, state_in.at("tfm_pos"), 1);
ggml_tensor * offset = ggml_arange(ctx0, 0.0f, (float) N, 1.0f);
ggml_tensor * pos = ggml_cast(ctx0, ggml_add(ctx0, offset, base), GGML_TYPE_I32);
q = ggml_rope_ext(ctx0, q, pos, nullptr, (int) d_head, GGML_ROPE_TYPE_NEOX, 0,
hparams.wav_tfm_rope_theta, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f);
k = ggml_rope_ext(ctx0, k, pos, nullptr, (int) d_head, GGML_ROPE_TYPE_NEOX, 0,
hparams.wav_tfm_rope_theta, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f);
// the position counter is the same for all layers, push it once from layer 0
if (il == 0) {
state_out.push_back({"tfm_pos", ggml_scale_bias(ctx0, state_in.at("tfm_pos"), 1.0f, (float) N)});
}
ggml_tensor * k_new = ggml_reshape_2d(ctx0, k, d_head * n_head_kv, N);
ggml_tensor * v_new = ggml_reshape_2d(ctx0, v, d_head * n_head_kv, N);
ggml_tensor * old_k = state_in.at("tfm_k_" + std::to_string(il)); // [d_head*n_head_kv, W-1]
ggml_tensor * old_v = state_in.at("tfm_v_" + std::to_string(il));
ggml_tensor * k_full = ggml_concat(ctx0, old_k, k_new, 1); // [.., prefix+N]
ggml_tensor * v_full = ggml_concat(ctx0, old_v, v_new, 1);
// next batch's prefix: the last (W-1) frames of this batch
state_out.push_back({"tfm_k_" + std::to_string(il),
ggml_cont(ctx0, ggml_view_2d(ctx0, k_full, k_full->ne[0], prefix, k_full->nb[1], (size_t) N * k_full->nb[1]))});
state_out.push_back({"tfm_v_" + std::to_string(il),
ggml_cont(ctx0, ggml_view_2d(ctx0, v_full, v_full->ne[0], prefix, v_full->nb[1], (size_t) N * v_full->nb[1]))});
// banded causal mask: key j is visible to query i iff 0 <= (prefix+i) - j < W
ggml_tensor * pos_k = ggml_reshape_2d(ctx0, ggml_arange(ctx0, 0.0f, (float) total_kv, 1.0f), total_kv, 1);
ggml_tensor * pos_q = ggml_reshape_2d(ctx0, ggml_arange(ctx0, (float) prefix, (float) (prefix + N), 1.0f), 1, N);
ggml_tensor * pos_q_grid = ggml_repeat_4d(ctx0, pos_q, total_kv, N, 1, 1);
ggml_tensor * diff = ggml_sub(ctx0, pos_q_grid, pos_k); // [total_kv, N]
ggml_tensor * causal_keep = ggml_step(ctx0, ggml_scale_bias(ctx0, diff, 1.0f, 0.5f)); // diff >= 0
ggml_tensor * in_window = ggml_step(ctx0, ggml_scale_bias(ctx0, diff, -1.0f, (float) W - 0.5f)); // diff < W
ggml_tensor * keep = ggml_mul(ctx0, causal_keep, in_window);
// on a cold start, key j is real state only when j >= prefix - tfm_pos, mask out the rest
ggml_tensor * warm = ggml_step(ctx0, ggml_scale_bias(ctx0, ggml_add(ctx0, pos_k, base),
1.0f, 0.5f - (float) prefix)); // j + pos > prefix - 0.5
keep = ggml_mul(ctx0, keep, warm);
ggml_tensor * mask = ggml_reshape_4d(ctx0, ggml_log(ctx0, keep), total_kv, N, 1, 1); // 0 = keep, -inf = masked
ggml_tensor * q_cur = ggml_reshape_4d(ctx0, q, d_head, n_head, N, 1);
ggml_tensor * k_cur = ggml_reshape_4d(ctx0, k_full, d_head, n_head_kv, total_kv, 1);
ggml_tensor * v_cur = ggml_reshape_4d(ctx0, v_full, d_head, n_head_kv, total_kv, 1);
ggml_tensor * attn_out = build_attn(layer.o_w, layer.o_b, q_cur, k_cur, v_cur, mask, kq_scale, il);
if (layer.ls_1_w) {
attn_out = ggml_mul(ctx0, attn_out, layer.ls_1_w);
}
cur = ggml_add(ctx0, residual, attn_out);
ggml_tensor * residual2 = cur;
ggml_tensor * h2 = ggml_rms_norm(ctx0, cur, hparams.wav_tfm_eps);
h2 = ggml_mul(ctx0, h2, layer.ln_2_w);
ggml_tensor * gate = ggml_mul_mat(ctx0, layer.ff_gate_w, h2);
ggml_tensor * up = ggml_mul_mat(ctx0, layer.ff_up_w, h2);
ggml_tensor * gu = ggml_swiglu_split(ctx0, gate, up);
ggml_tensor * down = ggml_mul_mat(ctx0, layer.ff_down_w, gu);
if (layer.ls_2_w) {
down = ggml_mul(ctx0, down, layer.ls_2_w);
}
return ggml_add(ctx0, residual2, down);
}
// dwconv -> LayerNorm -> pwconv1 -> GELU -> pwconv2 -> layer scale -> residual
// x: [T, C] T-first; LayerNorm/pwconv need C on ne0, so this transposes in and back out
ggml_tensor * clip_graph_qwen3tts_gen::code2wav::convnext_block(ggml_tensor * x, const clip_code2wav::upsample_block & blk, const std::string & state_prefix) const {
ggml_tensor * residual = x;
ggml_tensor * h = causal_conv1d_dw(x, blk.dwconv_w, blk.dwconv_b, state_prefix + "_dwconv"); // [T, C]
ggml_tensor * hc = ggml_cont(ctx0, ggml_transpose(ctx0, h)); // [C, T]
hc = ggml_norm(ctx0, hc, 1e-6f);
hc = ggml_mul(ctx0, hc, blk.norm_w);
hc = ggml_add(ctx0, hc, blk.norm_b);
ggml_tensor * g = ggml_mul_mat(ctx0, blk.pw1_w, hc);
g = ggml_add(ctx0, g, blk.pw1_b);
g = ggml_gelu(ctx0, g);
g = ggml_mul_mat(ctx0, blk.pw2_w, g);
g = ggml_add(ctx0, g, blk.pw2_b);
g = ggml_mul(ctx0, g, blk.gamma);
ggml_tensor * g_t = ggml_cont(ctx0, ggml_transpose(ctx0, g)); // back to [T, C]
return ggml_add(ctx0, residual, g_t);
}
// SnakeBeta -> dilated causal conv (k=7) -> SnakeBeta -> pointwise causal conv (k=1) -> residual.
// x: [T, C]. returns [T, C].
ggml_tensor * clip_graph_qwen3tts_gen::code2wav::dac_res_unit(ggml_tensor * x, const clip_code2wav::dac_res & res, int dilation, const std::string & state_name) const {
ggml_tensor * residual = x;
ggml_tensor * h = snake(x, res.act1_alpha, res.act1_beta);
h = causal_conv1d(h, res.conv1_w, res.conv1_b, dilation, state_name);
h = snake(h, res.act2_alpha, res.act2_beta);
h = causal_conv1d(h, res.conv2_w, res.conv2_b, 1, ""); // k=1, no left-context needed
return ggml_add(ctx0, residual, h);
}
// RVQ codes -> raw PCM for a batch of N = sliding_window frames
ggml_tensor * clip_graph_qwen3tts_gen::code2wav::decode(ggml_tensor * inp_codes) const {
const auto & c2w = model.c2w;
// 1. quantizer decode: N frames of 16 codes -> [512, N] (C-first)
ggml_tensor * hidden = quant_decode(inp_codes);
// 2. pre_conv: [512, N] -> T-first [N, 512] -> causal conv k=3 -> [N, 1024]
ggml_tensor * x = ggml_cont(ctx0, ggml_transpose(ctx0, hidden)); // [N, 512]
x = causal_conv1d(x, c2w.pre_conv_w, c2w.pre_conv_b, 1, "pre_conv"); // [N, 1024]
cb(x, "wav_pre_conv_out", -1);
// 3. pre_transformer: back to C-first [1024, N], project down, run the layers, project back up
ggml_tensor * cur = ggml_cont(ctx0, ggml_transpose(ctx0, x)); // [1024, N]
cur = ggml_mul_mat(ctx0, c2w.tfm_in_proj_w, cur);
cur = ggml_add(ctx0, cur, c2w.tfm_in_proj_b); // [512 (tfm hidden), N]
for (int il = 0; il < hparams.wav_tfm_n_layer; il++) {
cur = tfm_layer_forward(cur, c2w.tfm_layers[il], il);
}
cur = ggml_rms_norm(ctx0, cur, hparams.wav_tfm_eps);
cur = ggml_mul(ctx0, cur, c2w.tfm_output_norm_w);
cur = ggml_mul_mat(ctx0, c2w.tfm_out_proj_w, cur);
cur = ggml_add(ctx0, cur, c2w.tfm_out_proj_b); // [1024, N]
cb(cur, "wav_tfm_out", -1);
// 4. upsample: 2x (causal ConvTranspose1d, stride 2 + ConvNeXt block), back to T-first
// kernel == stride here, so there is no overlap tail to persist
x = ggml_cont(ctx0, ggml_transpose(ctx0, cur)); // [N, 1024]
for (size_t il = 0; il < c2w.upsample.size(); il++) {
const auto & up = c2w.upsample[il];
x = causal_conv_transpose1d(x, up.conv_w, up.conv_b, 2, "");
x = convnext_block(x, up, "up" + std::to_string(il));
cb(x, "wav_upsample_out", (int) il);
}
// 5. DAC decoder: conv_pre -> n blocks (SnakeBeta -> ConvTranspose1d -> 3 res units) -> conv_post
static constexpr int DAC_DILATIONS[3] = { 1, 3, 9 };
x = causal_conv1d(x, c2w.dac_entry_w, c2w.dac_entry_b, 1, "dac_entry");
cb(x, "wav_dac_entry_out", -1);
for (size_t il = 0; il < c2w.dac.size(); il++) {
const auto & blk = c2w.dac[il];
const int stride = (int) (blk.conv_w->ne[0] / 2); // kernel == 2*stride for all 4 blocks
const std::string blk_name = "dac" + std::to_string(il);
x = snake(x, blk.snake_alpha, blk.snake_beta);
x = causal_conv_transpose1d(x, blk.conv_w, blk.conv_b, stride, blk_name + "_tail");
for (size_t ir = 0; ir < blk.res.size(); ir++) {
x = dac_res_unit(x, blk.res[ir], DAC_DILATIONS[ir], blk_name + "_res" + std::to_string(ir));
}
cb(x, "wav_dac_block_out", (int) il);
}
x = snake(x, c2w.dac_post_snake_alpha, c2w.dac_post_snake_beta);
x = causal_conv1d(x, c2w.dac_post_conv_w, c2w.dac_post_conv_b, 1, "dac_post_conv"); // [n_samples, 1]
x = ggml_clamp(ctx0, x, -1.0f, 1.0f);
x = ggml_reshape_1d(ctx0, x, x->ne[0]);
cb(x, "wav_audio_out", -1);
return x;
}
// code2wav's persisted state buffers: RoPE position counter, K/V per pre_transformer layer,
// left-context/tail per stateful conv. shape lookup only, no graph needed
std::vector<c2w_state_slot> list_c2w_state_slots(const clip_hparams & hparams, const clip_model & model) {
const auto & c2w = model.c2w;
std::vector<c2w_state_slot> slots;
if (c2w.pre_conv_w == nullptr) {
return slots; // not a code2wav model, it keeps no state between calls
}
slots.push_back({"tfm_pos", 1, 1});
// prefix is (W-1) frames, the batch itself gives the other N=W frames (see tfm_layer_forward)
const int64_t d_head = c2w.tfm_layers[0].q_w->ne[1] / hparams.wav_tfm_n_head;
const int64_t kv_ch = d_head * hparams.wav_tfm_n_head_kv;
const int64_t prefix = hparams.wav_tfm_swa - 1;
for (int il = 0; il < hparams.wav_tfm_n_layer; il++) {
slots.push_back({"tfm_k_" + std::to_string(il), kv_ch, prefix});
slots.push_back({"tfm_v_" + std::to_string(il), kv_ch, prefix});
}
slots.push_back({"pre_conv", c2w.pre_conv_w->ne[0] - 1, c2w.pre_conv_w->ne[1]});
for (size_t il = 0; il < c2w.upsample.size(); il++) {
const auto & up = c2w.upsample[il];
slots.push_back({"up" + std::to_string(il) + "_dwconv", up.dwconv_w->ne[0] - 1, up.dwconv_w->ne[2]});
}
slots.push_back({"dac_entry", c2w.dac_entry_w->ne[0] - 1, c2w.dac_entry_w->ne[1]});
static constexpr int DAC_DILATIONS[3] = { 1, 3, 9 };
for (size_t il = 0; il < c2w.dac.size(); il++) {
const auto & blk = c2w.dac[il];
const int64_t stride = blk.conv_w->ne[0] / 2; // kernel == 2*stride for all 4 blocks
const std::string blk_name = "dac" + std::to_string(il);
slots.push_back({blk_name + "_tail", stride, blk.conv_w->ne[1]});
for (size_t ir = 0; ir < blk.res.size(); ir++) {
const auto & res = blk.res[ir];
slots.push_back({blk_name + "_res" + std::to_string(ir),
(res.conv1_w->ne[0] - 1) * DAC_DILATIONS[ir], res.conv1_w->ne[1]});
}
}
slots.push_back({"dac_post_conv", c2w.dac_post_conv_w->ne[0] - 1, c2w.dac_post_conv_w->ne[1]});
return slots;
}
// both sub-graphs are always built, so the topology stays constant
// ggml_build_forward_select() then picks the one that actually runs
ggml_cgraph * clip_graph_qwen3tts_gen::build() {
GGML_ASSERT(n_batch == 1); // this module only ever processes one frame at a time
int idx;
switch (gen_process) {
case CLIP_GEN_PROCESS_GEN_CODE: idx = 0; break;
case CLIP_GEN_PROCESS_GEN_WAV: idx = 1; break;
default: GGML_ABORT("unknown gen_process");
}
// ---- CLIP_GEN_PROCESS_GEN_CODE: backbone hidden state -> 16 RVQ codes + next-step embd ----
// not build_inp_raw(), a GEN_WAV call's `img` has no hidden-state data
ggml_tensor * h_state = ggml_new_tensor_1d(ctx0, GGML_TYPE_F32, n_mmproj_embd);
ggml_set_name(h_state, "inp_raw"); // must keep this exact name, clip_encode() sets it by name
ggml_set_input(h_state);
ggml_tensor * code0 = ggml_new_tensor_1d(ctx0, GGML_TYPE_I32, 1);
ggml_set_name(code0, "inp_code0");
ggml_set_input(code0);
ggml_tensor * code0_embd = ggml_get_rows(ctx0, model.gen_code_out_embd_w, code0);
code0_embd = ggml_reshape_1d(ctx0, code0_embd, code0_embd->ne[0]);
cb(code0_embd, "code0_embd", -1);
const int64_t n_acoustic = model.gen_code_head_w->ne[2]; // 15
const int n_codes = (int) n_acoustic + 1; // 16
const int64_t n_kv_pad = n_codes;
const int n_layer = (int) model.layers.size();
const int n_head = hparams.n_head;
const int n_head_kv = hparams.n_head_kv;
const int64_t d_head = model.layers[0].q_w->ne[1] / n_head;
// zero-filled per layer k/v caches, so masked-out rows can't hold garbage
std::vector<ggml_tensor *> k_cache(n_layer), v_cache(n_layer);
for (int il = 0; il < n_layer; il++) {
k_cache[il] = ggml_fill(ctx0, ggml_new_tensor_2d(ctx0, GGML_TYPE_F32, d_head * n_head_kv, n_kv_pad), 0.0f);
v_cache[il] = ggml_fill(ctx0, ggml_new_tensor_2d(ctx0, GGML_TYPE_F32, d_head * n_head_kv, n_kv_pad), 0.0f);
}
code_gen cg(*this, top_k, top_p);
ggml_tensor * out_code_cache = ggml_new_tensor_2d(ctx0, GGML_TYPE_I32, 1, n_codes);
out_code_cache = cg.cache_set(out_code_cache, 0, code0);
ggml_tensor * inp_rand0 = ggml_new_tensor_1d(ctx0, GGML_TYPE_F32, 1);
ggml_set_name(inp_rand0, "inp_rand_0");
ggml_set_input(inp_rand0);
cg.prefill(k_cache, v_cache, out_code_cache, h_state, code0_embd, inp_rand0);
for (int g = 1; g < n_acoustic; g++) {
ggml_tensor * inp_rand = ggml_new_tensor_1d(ctx0, GGML_TYPE_F32, 1);
ggml_set_name(inp_rand, ("inp_rand_" + std::to_string(g)).c_str());
ggml_set_input(inp_rand);
out_code_cache = cg.step(k_cache, v_cache, out_code_cache, inp_rand, g);
}
// output 1: this frame's 16 sampled codes, for the caller's code2wav window
ggml_tensor * out_codes = ggml_cont(ctx0, out_code_cache);
ggml_set_name(out_codes, "out_codes");
ggml_set_output(out_codes);
// output 2: sum of all 16 codebook embeddings, fed back to the talker for the next frame
ggml_tensor * out_embd = code0_embd;
for (int g = 1; g <= n_acoustic; g++) {
ggml_tensor * code_g = ggml_view_1d(ctx0, out_code_cache, 1, (size_t) g * out_code_cache->nb[1]);
ggml_tensor * embd_g = ggml_view_2d(ctx0, model.gen_code_embd_w, model.gen_code_embd_w->ne[0], model.gen_code_embd_w->ne[1],
model.gen_code_embd_w->nb[1], (size_t) (g - 1) * model.gen_code_embd_w->nb[2]);
ggml_tensor * e = ggml_get_rows(ctx0, embd_g, code_g);
e = ggml_reshape_1d(ctx0, e, e->ne[0]);
out_embd = ggml_add(ctx0, out_embd, e);
}
out_embd = ggml_reshape_2d(ctx0, out_embd, out_embd->ne[0], 1);
cb(out_embd, "gen_audio_out", -1);
// ---- CLIP_GEN_PROCESS_GEN_WAV: 16 RVQ codes -> raw PCM ----
const int n_frames = hparams.wav_tfm_swa; // frames per batch, == the attention window
ggml_tensor * inp_codes = ggml_new_tensor_2d(ctx0, GGML_TYPE_I32, n_frames, n_codes);
ggml_set_name(inp_codes, "inp_codes");
ggml_set_input(inp_codes);
code2wav c2w(*this);
for (const auto & slot : list_c2w_state_slots(hparams, model)) {
ggml_tensor * t = ggml_new_tensor_2d(ctx0, GGML_TYPE_F32, slot.ne0, slot.ne1);
ggml_set_name(t, ("state_in_" + slot.name).c_str());
ggml_set_input(t);
c2w.state_in[slot.name] = t;
}
ggml_tensor * out_audio = c2w.decode(inp_codes);
ggml_set_name(out_audio, "out_audio");
ggml_set_output(out_audio);
for (auto & slot : c2w.state_out) {
ggml_set_name(slot.second, ("state_out_" + slot.first).c_str());
ggml_set_output(slot.second);
}
// out_embd goes last, clip_encode() reads it back via ggml_graph_node(gf, -1)
ggml_tensor * outs[2];
outs[0] = out_codes; outs[1] = out_audio;
ggml_build_forward_select(gf, outs, 2, idx);
for (auto & slot : c2w.state_out) {
outs[0] = out_codes; outs[1] = slot.second;
ggml_build_forward_select(gf, outs, 2, idx);
}
outs[0] = out_embd; outs[1] = out_audio;
ggml_build_forward_select(gf, outs, 2, idx);
return gf;
}