align symbol naming on omnivoice convention
This commit is contained in:
+21
-9
@@ -63,7 +63,9 @@ static ggml_backend_t cpu_backend_new(int n_threads) {
|
||||
// Initialize backends: load all available (CUDA, Metal, Vulkan...),
|
||||
// pick the best one, keep CPU as fallback.
|
||||
// label: log prefix, e.g. "DiT", "VAE", "LM"
|
||||
// Subsequent calls reuse the same backend (single VMM pool).
|
||||
// Subsequent calls reuse the same backend (single VMM pool). Returns a
|
||||
// BackendPair with .backend == NULL when initialisation fails; the caller
|
||||
// must check this before passing it to any pipeline_*_load.
|
||||
static BackendPair backend_init(const char * label) {
|
||||
if (g_backend_refs > 0) {
|
||||
g_backend_refs++;
|
||||
@@ -80,20 +82,26 @@ static BackendPair backend_init(const char * label) {
|
||||
if (force_backend) {
|
||||
bp.backend = ggml_backend_init_by_name(force_backend, nullptr);
|
||||
if (!bp.backend) {
|
||||
std::string avail;
|
||||
// Assemble the device list inline so the log callback gets one
|
||||
// self-contained line instead of three. The available list can
|
||||
// grow with each backend that registers, so a std::string here
|
||||
// keeps the formatting allocation-free for the common case.
|
||||
std::string msg = "[Load] GGML_BACKEND=";
|
||||
msg += force_backend;
|
||||
msg += " not found. Available:";
|
||||
for (size_t i = 0; i < ggml_backend_dev_count(); i++) {
|
||||
if (i > 0) {
|
||||
avail += " ";
|
||||
}
|
||||
avail += ggml_backend_dev_name(ggml_backend_dev_get(i));
|
||||
msg += ' ';
|
||||
msg += ggml_backend_dev_name(ggml_backend_dev_get(i));
|
||||
}
|
||||
qt_throw("GGML_BACKEND=%s not found. Available: %s", force_backend, avail.c_str());
|
||||
qt_log(QT_LOG_ERROR, "%s", msg.c_str());
|
||||
return BackendPair{};
|
||||
}
|
||||
} else {
|
||||
bp.backend = ggml_backend_init_best();
|
||||
}
|
||||
if (!bp.backend) {
|
||||
qt_throw("no backend available");
|
||||
qt_log(QT_LOG_ERROR, "[Load] no backend available");
|
||||
return BackendPair{};
|
||||
}
|
||||
bool best_is_cpu = (strcmp(ggml_backend_name(bp.backend), "CPU") == 0);
|
||||
int n_threads = backend_cpu_n_threads();
|
||||
@@ -105,7 +113,11 @@ static BackendPair backend_init(const char * label) {
|
||||
bp.cpu_backend = cpu_backend_new(n_threads);
|
||||
}
|
||||
if (!bp.cpu_backend) {
|
||||
qt_throw("failed to init CPU backend");
|
||||
qt_log(QT_LOG_ERROR, "[Load] failed to init CPU backend");
|
||||
if (bp.backend && bp.backend != bp.cpu_backend) {
|
||||
ggml_backend_free(bp.backend);
|
||||
}
|
||||
return BackendPair{};
|
||||
}
|
||||
bp.has_gpu = !best_is_cpu;
|
||||
qt_log(QT_LOG_INFO, "[Load] %s backend: %s (CPU threads: %d)", label, ggml_backend_name(bp.backend), n_threads);
|
||||
|
||||
@@ -133,14 +133,14 @@ static struct ggml_tensor * qwen_causal_trans_conv1d(struct ggml_context * ctx,
|
||||
// w: [k, IC, OC] f32, source layout (K, IC, OC) maps to ggml ne directly
|
||||
// b: [OC] f32 or NULL
|
||||
// x: [T, IC] f32 T-first
|
||||
// pad_mode: QWEN_PAD_CONSTANT (zero pad, default for SEANet and the DAC
|
||||
// decoder) or QWEN_PAD_REPLICATE (edge pad, replicates the
|
||||
// pad_mode: CTC_PAD_CONSTANT (zero pad, default for SEANet and the DAC
|
||||
// decoder) or CTC_PAD_REPLICATE (edge pad, replicates the
|
||||
// first / last frame to match Mimi's downsample which is the
|
||||
// only conv passing pad_mode="replicate" upstream).
|
||||
// Returns [ceil(T / stride), OC] f32 T-first.
|
||||
enum QwenPadMode {
|
||||
QWEN_PAD_CONSTANT = 0,
|
||||
QWEN_PAD_REPLICATE = 1,
|
||||
CTC_PAD_CONSTANT = 0,
|
||||
CTC_PAD_REPLICATE = 1,
|
||||
};
|
||||
|
||||
static struct ggml_tensor * qwen_causal_conv1d(struct ggml_context * ctx,
|
||||
@@ -150,7 +150,7 @@ static struct ggml_tensor * qwen_causal_conv1d(struct ggml_context * ctx,
|
||||
int k,
|
||||
int d,
|
||||
int s = 1,
|
||||
int pad_mode = QWEN_PAD_CONSTANT) {
|
||||
int pad_mode = CTC_PAD_CONSTANT) {
|
||||
int OC = (int) w->ne[2];
|
||||
int kernel_eff = (k - 1) * d + 1;
|
||||
int padding_tot = kernel_eff - s;
|
||||
@@ -167,7 +167,7 @@ static struct ggml_tensor * qwen_causal_conv1d(struct ggml_context * ctx,
|
||||
}
|
||||
|
||||
struct ggml_tensor * y = x;
|
||||
if (pad_mode == QWEN_PAD_REPLICATE) {
|
||||
if (pad_mode == CTC_PAD_REPLICATE) {
|
||||
// Edge pad: repeat x[t=0] padding_tot times on the left and x[t=T-1]
|
||||
// extra_pad times on the right via a single ggml_repeat per side.
|
||||
int IC = (int) x->ne[1];
|
||||
|
||||
+20
-20
@@ -21,7 +21,7 @@
|
||||
#include <cstdlib>
|
||||
#include <string>
|
||||
|
||||
#define QWEN_UPSAMPLE_MAX_BLOCKS 2
|
||||
#define UPSAMPLE_MAX_BLOCKS 2
|
||||
|
||||
struct QwenConvNeXtBlock {
|
||||
struct ggml_tensor * dwconv_w; // [K=7, 1, C] depthwise weight
|
||||
@@ -36,14 +36,14 @@ struct QwenConvNeXtBlock {
|
||||
};
|
||||
|
||||
struct QwenUpsampleStage {
|
||||
int num_blocks; // 2
|
||||
int channels; // 1024 (= latent_dim)
|
||||
int upsample_ratio; // 2 per block, 4x total
|
||||
int dwconv_kernel; // 7
|
||||
int num_blocks; // 2
|
||||
int channels; // 1024 (= latent_dim)
|
||||
int upsample_ratio; // 2 per block, 4x total
|
||||
int dwconv_kernel; // 7
|
||||
|
||||
struct ggml_tensor * transconv_w[QWEN_UPSAMPLE_MAX_BLOCKS]; // pre-permuted [IC, K*OC]
|
||||
struct ggml_tensor * transconv_b[QWEN_UPSAMPLE_MAX_BLOCKS]; // [OC]
|
||||
QwenConvNeXtBlock convnext[QWEN_UPSAMPLE_MAX_BLOCKS];
|
||||
struct ggml_tensor * transconv_w[UPSAMPLE_MAX_BLOCKS]; // pre-permuted [IC, K*OC]
|
||||
struct ggml_tensor * transconv_b[UPSAMPLE_MAX_BLOCKS]; // [OC]
|
||||
QwenConvNeXtBlock convnext[UPSAMPLE_MAX_BLOCKS];
|
||||
|
||||
struct ggml_context * weight_ctx;
|
||||
ggml_backend_buffer_t weight_buf;
|
||||
@@ -51,15 +51,15 @@ struct QwenUpsampleStage {
|
||||
|
||||
// Read upsample hyperparameters from GGUF metadata, allocate every weight
|
||||
// tensor on the backend, and bind tensor pointers in the struct.
|
||||
static bool qwen_upsample_stage_load(QwenUpsampleStage * stage, const GGUFModel & gf, ggml_backend_t backend) {
|
||||
static bool upsample_stage_load(QwenUpsampleStage * stage, const GGUFModel & gf, ggml_backend_t backend) {
|
||||
stage->channels = (int) gf_get_u32(gf, "qwen3-tts-tokenizer.decoder.latent_dim");
|
||||
stage->dwconv_kernel = 7;
|
||||
stage->upsample_ratio = 2;
|
||||
stage->num_blocks = 2;
|
||||
|
||||
if (stage->num_blocks > QWEN_UPSAMPLE_MAX_BLOCKS) {
|
||||
if (stage->num_blocks > UPSAMPLE_MAX_BLOCKS) {
|
||||
fprintf(stderr, "[Upsample] FATAL: %d blocks exceeds compile-time max %d\n", stage->num_blocks,
|
||||
QWEN_UPSAMPLE_MAX_BLOCKS);
|
||||
UPSAMPLE_MAX_BLOCKS);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -110,7 +110,7 @@ static bool qwen_upsample_stage_load(QwenUpsampleStage * stage, const GGUFModel
|
||||
return true;
|
||||
}
|
||||
|
||||
static void qwen_upsample_stage_free(QwenUpsampleStage * stage) {
|
||||
static void upsample_stage_free(QwenUpsampleStage * stage) {
|
||||
if (stage->weight_buf) {
|
||||
ggml_backend_buffer_free(stage->weight_buf);
|
||||
stage->weight_buf = NULL;
|
||||
@@ -124,10 +124,10 @@ static void qwen_upsample_stage_free(QwenUpsampleStage * stage) {
|
||||
// One ConvNeXt block forward.
|
||||
// x: [T, C] f32 T-first
|
||||
// returns [T, C] f32 T-first
|
||||
static struct ggml_tensor * qwen_convnext_block_forward(struct ggml_context * ctx,
|
||||
const QwenConvNeXtBlock & block,
|
||||
struct ggml_tensor * x,
|
||||
int kernel) {
|
||||
static struct ggml_tensor * convnext_block_forward(struct ggml_context * ctx,
|
||||
const QwenConvNeXtBlock & block,
|
||||
struct ggml_tensor * x,
|
||||
int kernel) {
|
||||
int T = (int) x->ne[0];
|
||||
int C = (int) x->ne[1];
|
||||
|
||||
@@ -178,14 +178,14 @@ static struct ggml_tensor * qwen_convnext_block_forward(struct ggml_context *
|
||||
// The top-level upsample stage uses kernel == stride (no causal trim).
|
||||
// The DAC decoder blocks (separate header) use kernel == 2 * stride
|
||||
// with a stride-frame causal trim.
|
||||
static struct ggml_tensor * qwen_upsample_stage_forward(struct ggml_context * ctx,
|
||||
const QwenUpsampleStage * stage,
|
||||
struct ggml_tensor * x) {
|
||||
static struct ggml_tensor * upsample_stage_forward(struct ggml_context * ctx,
|
||||
const QwenUpsampleStage * stage,
|
||||
struct ggml_tensor * x) {
|
||||
int kernel = stage->upsample_ratio;
|
||||
for (int i = 0; i < stage->num_blocks; i++) {
|
||||
x = qwen_causal_trans_conv1d(ctx, stage->transconv_w[i], stage->transconv_b[i], x, stage->upsample_ratio,
|
||||
kernel, stage->channels);
|
||||
x = qwen_convnext_block_forward(ctx, stage->convnext[i], x, stage->dwconv_kernel);
|
||||
x = convnext_block_forward(ctx, stage->convnext[i], x, stage->dwconv_kernel);
|
||||
}
|
||||
return x;
|
||||
}
|
||||
|
||||
+39
-43
@@ -35,8 +35,8 @@
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#define QWEN_DAC_NUM_BLOCKS 4
|
||||
#define QWEN_DAC_RES_UNITS 3
|
||||
#define DAC_NUM_BLOCKS 4
|
||||
#define DAC_RES_UNITS 3
|
||||
|
||||
// SnakeBeta runtime parameters with exp() folded in: a holds exp(alpha)
|
||||
// and inv_b holds 1 / (exp(beta) + 1e-9). Layout [1, C] f32 matches the
|
||||
@@ -65,7 +65,7 @@ struct QwenDACBlock {
|
||||
QwenDACSnake snake1;
|
||||
struct ggml_tensor * tcw; // [IC, K*OC] f32, pre-permuted from (IC, OC, K)
|
||||
struct ggml_tensor * tcb; // [OC] f32
|
||||
QwenDACResUnit ru[QWEN_DAC_RES_UNITS];
|
||||
QwenDACResUnit ru[DAC_RES_UNITS];
|
||||
int in_ch;
|
||||
int out_ch;
|
||||
int stride;
|
||||
@@ -77,15 +77,15 @@ struct QwenDACDecoder {
|
||||
struct ggml_tensor * conv_pre_w; // [7, 1024, 1536] f32
|
||||
struct ggml_tensor * conv_pre_b; // [1536] f32
|
||||
|
||||
QwenDACBlock blk[QWEN_DAC_NUM_BLOCKS];
|
||||
QwenDACBlock blk[DAC_NUM_BLOCKS];
|
||||
|
||||
QwenDACSnake snake_post; // 96 channels
|
||||
|
||||
// final conv: 96 -> 1, k=7, causal
|
||||
struct ggml_tensor * conv_post_w; // [7, 96, 1] f32
|
||||
struct ggml_tensor * conv_post_b; // [1] f32
|
||||
struct ggml_tensor * conv_post_w; // [7, 96, 1] f32
|
||||
struct ggml_tensor * conv_post_b; // [1] f32
|
||||
|
||||
int channels[QWEN_DAC_NUM_BLOCKS + 1]; // 1536, 768, 384, 192, 96
|
||||
int channels[DAC_NUM_BLOCKS + 1]; // 1536, 768, 384, 192, 96
|
||||
|
||||
struct ggml_context * weight_ctx;
|
||||
ggml_backend_buffer_t weight_buf;
|
||||
@@ -93,11 +93,11 @@ struct QwenDACDecoder {
|
||||
|
||||
// Read alpha and beta from the GGUF, fold exp() and reciprocal CPU-side,
|
||||
// and bind two [1, C] f32 tensors on the backend ctx as a and inv_b.
|
||||
static void qwen_dac_load_snakebeta(WeightCtx * wctx,
|
||||
const GGUFModel & gf,
|
||||
QwenDACSnake * s,
|
||||
const std::string & alpha_name,
|
||||
const std::string & beta_name) {
|
||||
static void dac_load_snakebeta(WeightCtx * wctx,
|
||||
const GGUFModel & gf,
|
||||
QwenDACSnake * s,
|
||||
const std::string & alpha_name,
|
||||
const std::string & beta_name) {
|
||||
struct ggml_tensor * alpha_meta = ggml_get_tensor(gf.meta, alpha_name.c_str());
|
||||
struct ggml_tensor * beta_meta = ggml_get_tensor(gf.meta, beta_name.c_str());
|
||||
if (!alpha_meta || !beta_meta) {
|
||||
@@ -134,12 +134,12 @@ static void qwen_dac_load_snakebeta(WeightCtx * wctx,
|
||||
|
||||
// Allocate every weight tensor on the backend, copy from the GGUF mapping
|
||||
// with per-tensor transforms (snake exp/reciprocal, transconv permute).
|
||||
static bool qwen_dac_decoder_load(QwenDACDecoder * d, const GGUFModel & gf, ggml_backend_t backend) {
|
||||
static const int strides[QWEN_DAC_NUM_BLOCKS] = { 8, 5, 4, 3 };
|
||||
static const int chs[QWEN_DAC_NUM_BLOCKS + 1] = { 1536, 768, 384, 192, 96 };
|
||||
static const int dilations[QWEN_DAC_RES_UNITS] = { 1, 3, 9 };
|
||||
static bool dac_decoder_load(QwenDACDecoder * d, const GGUFModel & gf, ggml_backend_t backend) {
|
||||
static const int strides[DAC_NUM_BLOCKS] = { 8, 5, 4, 3 };
|
||||
static const int chs[DAC_NUM_BLOCKS + 1] = { 1536, 768, 384, 192, 96 };
|
||||
static const int dilations[DAC_RES_UNITS] = { 1, 3, 9 };
|
||||
|
||||
for (int i = 0; i <= QWEN_DAC_NUM_BLOCKS; i++) {
|
||||
for (int i = 0; i <= DAC_NUM_BLOCKS; i++) {
|
||||
d->channels[i] = chs[i];
|
||||
}
|
||||
|
||||
@@ -150,7 +150,7 @@ static bool qwen_dac_decoder_load(QwenDACDecoder * d, const GGUFModel & gf, ggml
|
||||
d->conv_pre_w = gf_load_conv(&wctx, gf, "tok_dec.dec.0.conv.weight");
|
||||
d->conv_pre_b = gf_load_tensor(&wctx, gf, "tok_dec.dec.0.conv.bias");
|
||||
|
||||
for (int i = 0; i < QWEN_DAC_NUM_BLOCKS; i++) {
|
||||
for (int i = 0; i < DAC_NUM_BLOCKS; i++) {
|
||||
QwenDACBlock & b = d->blk[i];
|
||||
b.in_ch = chs[i];
|
||||
b.out_ch = chs[i + 1];
|
||||
@@ -164,31 +164,29 @@ static bool qwen_dac_decoder_load(QwenDACDecoder * d, const GGUFModel & gf, ggml
|
||||
char prefix[64];
|
||||
snprintf(prefix, sizeof(prefix), "tok_dec.dec.%d", py_idx);
|
||||
|
||||
qwen_dac_load_snakebeta(&wctx, gf, &b.snake1, std::string(prefix) + ".snake.alpha",
|
||||
std::string(prefix) + ".snake.beta");
|
||||
dac_load_snakebeta(&wctx, gf, &b.snake1, std::string(prefix) + ".snake.alpha",
|
||||
std::string(prefix) + ".snake.beta");
|
||||
|
||||
b.tcw = qwen_load_ctw_f32(&wctx, gf, std::string(prefix) + ".conv_t.weight");
|
||||
b.tcb = gf_load_tensor(&wctx, gf, std::string(prefix) + ".conv_t.bias");
|
||||
|
||||
for (int r = 0; r < QWEN_DAC_RES_UNITS; r++) {
|
||||
for (int r = 0; r < DAC_RES_UNITS; r++) {
|
||||
QwenDACResUnit & ru = b.ru[r];
|
||||
ru.dilation = dilations[r];
|
||||
|
||||
char rp[96];
|
||||
snprintf(rp, sizeof(rp), "%s.res.%d", prefix, r);
|
||||
|
||||
qwen_dac_load_snakebeta(&wctx, gf, &ru.act1, std::string(rp) + ".act1.alpha",
|
||||
std::string(rp) + ".act1.beta");
|
||||
dac_load_snakebeta(&wctx, gf, &ru.act1, std::string(rp) + ".act1.alpha", std::string(rp) + ".act1.beta");
|
||||
ru.c1w = gf_load_conv(&wctx, gf, std::string(rp) + ".conv1.weight");
|
||||
ru.c1b = gf_load_tensor(&wctx, gf, std::string(rp) + ".conv1.bias");
|
||||
qwen_dac_load_snakebeta(&wctx, gf, &ru.act2, std::string(rp) + ".act2.alpha",
|
||||
std::string(rp) + ".act2.beta");
|
||||
dac_load_snakebeta(&wctx, gf, &ru.act2, std::string(rp) + ".act2.alpha", std::string(rp) + ".act2.beta");
|
||||
ru.c2w = gf_load_conv(&wctx, gf, std::string(rp) + ".conv2.weight");
|
||||
ru.c2b = gf_load_tensor(&wctx, gf, std::string(rp) + ".conv2.bias");
|
||||
}
|
||||
}
|
||||
|
||||
qwen_dac_load_snakebeta(&wctx, gf, &d->snake_post, "tok_dec.dec.5.snake.alpha", "tok_dec.dec.5.snake.beta");
|
||||
dac_load_snakebeta(&wctx, gf, &d->snake_post, "tok_dec.dec.5.snake.alpha", "tok_dec.dec.5.snake.beta");
|
||||
d->conv_post_w = gf_load_conv(&wctx, gf, "tok_dec.dec.6.conv.weight");
|
||||
d->conv_post_b = gf_load_tensor(&wctx, gf, "tok_dec.dec.6.conv.bias");
|
||||
|
||||
@@ -199,12 +197,12 @@ static bool qwen_dac_decoder_load(QwenDACDecoder * d, const GGUFModel & gf, ggml
|
||||
d->weight_ctx = wctx.ctx;
|
||||
d->weight_buf = wctx.buffer;
|
||||
|
||||
fprintf(stderr, "[DAC] Loaded: %d blocks (strides 8/5/4/3), 24 kHz mono out, weights %.1f MB\n",
|
||||
QWEN_DAC_NUM_BLOCKS, (float) ggml_backend_buffer_get_size(d->weight_buf) / (1024.0f * 1024.0f));
|
||||
fprintf(stderr, "[DAC] Loaded: %d blocks (strides 8/5/4/3), 24 kHz mono out, weights %.1f MB\n", DAC_NUM_BLOCKS,
|
||||
(float) ggml_backend_buffer_get_size(d->weight_buf) / (1024.0f * 1024.0f));
|
||||
return true;
|
||||
}
|
||||
|
||||
static void qwen_dac_decoder_free(QwenDACDecoder * d) {
|
||||
static void dac_decoder_free(QwenDACDecoder * d) {
|
||||
if (d->weight_buf) {
|
||||
ggml_backend_buffer_free(d->weight_buf);
|
||||
d->weight_buf = NULL;
|
||||
@@ -219,7 +217,7 @@ static void qwen_dac_decoder_free(QwenDACDecoder * d) {
|
||||
// ops so the backend graph optimiser fuses them into a dedicated snake
|
||||
// kernel where one is available, and falls back to a plain CPU/GPU op
|
||||
// chain otherwise. x [T, C] T-first, a and inv_b broadcast on the C axis.
|
||||
static struct ggml_tensor * qwen_dac_snake(struct ggml_context * ctx, struct ggml_tensor * x, const QwenDACSnake & s) {
|
||||
static struct ggml_tensor * dac_snake(struct ggml_context * ctx, struct ggml_tensor * x, const QwenDACSnake & s) {
|
||||
struct ggml_tensor * t = ggml_mul(ctx, x, s.a); // a * x (broadcast over T)
|
||||
t = ggml_sin(ctx, t); // sin(a * x)
|
||||
t = ggml_sqr(ctx, t); // sin^2(a * x)
|
||||
@@ -232,13 +230,11 @@ static struct ggml_tensor * qwen_dac_snake(struct ggml_context * ctx, struct ggm
|
||||
// and the DAC share the same primitive.
|
||||
|
||||
// Residual unit forward: skip + conv2(snake(conv1(snake(x)))).
|
||||
static struct ggml_tensor * qwen_dac_res_unit(struct ggml_context * ctx,
|
||||
const QwenDACResUnit * ru,
|
||||
struct ggml_tensor * x) {
|
||||
static struct ggml_tensor * dac_res_unit(struct ggml_context * ctx, const QwenDACResUnit * ru, struct ggml_tensor * x) {
|
||||
struct ggml_tensor * skip = x;
|
||||
x = qwen_dac_snake(ctx, x, ru->act1);
|
||||
x = dac_snake(ctx, x, ru->act1);
|
||||
x = qwen_causal_conv1d(ctx, ru->c1w, ru->c1b, x, 7, ru->dilation);
|
||||
x = qwen_dac_snake(ctx, x, ru->act2);
|
||||
x = dac_snake(ctx, x, ru->act2);
|
||||
x = qwen_causal_conv1d(ctx, ru->c2w, ru->c2b, x, 1, 1);
|
||||
return ggml_add(ctx, skip, x);
|
||||
}
|
||||
@@ -247,21 +243,21 @@ static struct ggml_tensor * qwen_dac_res_unit(struct ggml_context * ctx,
|
||||
// x: [T, 1024] f32 T-first
|
||||
// returns [T * 1920, 1] f32 T-first (raw audio samples @ 24 kHz mono,
|
||||
// without final clamp; the orchestration layer clips to [-1, 1]).
|
||||
static struct ggml_tensor * qwen_dac_decoder_forward(struct ggml_context * ctx,
|
||||
const QwenDACDecoder * d,
|
||||
struct ggml_tensor * x) {
|
||||
static struct ggml_tensor * dac_decoder_forward(struct ggml_context * ctx,
|
||||
const QwenDACDecoder * d,
|
||||
struct ggml_tensor * x) {
|
||||
x = qwen_causal_conv1d(ctx, d->conv_pre_w, d->conv_pre_b, x, 7, 1);
|
||||
|
||||
for (int i = 0; i < QWEN_DAC_NUM_BLOCKS; i++) {
|
||||
for (int i = 0; i < DAC_NUM_BLOCKS; i++) {
|
||||
const QwenDACBlock & b = d->blk[i];
|
||||
x = qwen_dac_snake(ctx, x, b.snake1);
|
||||
x = dac_snake(ctx, x, b.snake1);
|
||||
x = qwen_causal_trans_conv1d(ctx, b.tcw, b.tcb, x, b.stride, b.kernel, b.out_ch);
|
||||
for (int r = 0; r < QWEN_DAC_RES_UNITS; r++) {
|
||||
x = qwen_dac_res_unit(ctx, &b.ru[r], x);
|
||||
for (int r = 0; r < DAC_RES_UNITS; r++) {
|
||||
x = dac_res_unit(ctx, &b.ru[r], x);
|
||||
}
|
||||
}
|
||||
|
||||
x = qwen_dac_snake(ctx, x, d->snake_post);
|
||||
x = dac_snake(ctx, x, d->snake_post);
|
||||
x = qwen_causal_conv1d(ctx, d->conv_post_w, d->conv_post_b, x, 7, 1);
|
||||
return x;
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ struct QwenEncoderDownsample {
|
||||
ggml_backend_buffer_t weight_buf;
|
||||
};
|
||||
|
||||
static bool qwen_encoder_downsample_load(QwenEncoderDownsample * d, const GGUFModel & gf, ggml_backend_t backend) {
|
||||
static bool enc_down_load(QwenEncoderDownsample * d, const GGUFModel & gf, ggml_backend_t backend) {
|
||||
d->kernel = 4;
|
||||
d->stride = 2;
|
||||
|
||||
@@ -46,7 +46,7 @@ static bool qwen_encoder_downsample_load(QwenEncoderDownsample * d, const GGUFMo
|
||||
return true;
|
||||
}
|
||||
|
||||
static void qwen_encoder_downsample_free(QwenEncoderDownsample * d) {
|
||||
static void enc_down_free(QwenEncoderDownsample * d) {
|
||||
if (d->weight_buf) {
|
||||
ggml_backend_buffer_free(d->weight_buf);
|
||||
d->weight_buf = NULL;
|
||||
@@ -63,8 +63,8 @@ static void qwen_encoder_downsample_free(QwenEncoderDownsample * d) {
|
||||
// and the encoder transformer which inherit config.pad_mode='constant'.
|
||||
// x: [T, 512] f32 T-first
|
||||
// Returns [ceil(T/2), 512] f32 T-first.
|
||||
static struct ggml_tensor * qwen_encoder_downsample_forward(struct ggml_context * ctx,
|
||||
const QwenEncoderDownsample * d,
|
||||
struct ggml_tensor * x) {
|
||||
return qwen_causal_conv1d(ctx, d->weight, NULL, x, d->kernel, 1, d->stride, QWEN_PAD_REPLICATE);
|
||||
static struct ggml_tensor * enc_down_forward(struct ggml_context * ctx,
|
||||
const QwenEncoderDownsample * d,
|
||||
struct ggml_tensor * x) {
|
||||
return qwen_causal_conv1d(ctx, d->weight, NULL, x, d->kernel, 1, d->stride, CTC_PAD_REPLICATE);
|
||||
}
|
||||
|
||||
+21
-21
@@ -30,7 +30,7 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#define QWEN_ENCODER_TRANSFORMER_MAX_LAYERS 16
|
||||
#define ENC_TRANS_MAX_LAYERS 16
|
||||
|
||||
struct QwenEncoderTransformerLayer {
|
||||
// Pre-attention LayerNorm
|
||||
@@ -63,13 +63,13 @@ struct QwenEncoderTransformer {
|
||||
float rope_theta;
|
||||
float norm_eps;
|
||||
|
||||
QwenEncoderTransformerLayer layers[QWEN_ENCODER_TRANSFORMER_MAX_LAYERS];
|
||||
QwenEncoderTransformerLayer layers[ENC_TRANS_MAX_LAYERS];
|
||||
|
||||
struct ggml_context * weight_ctx;
|
||||
ggml_backend_buffer_t weight_buf;
|
||||
};
|
||||
|
||||
static bool qwen_encoder_transformer_load(QwenEncoderTransformer * tr, const GGUFModel & gf, ggml_backend_t backend) {
|
||||
static bool enc_trans_load(QwenEncoderTransformer * tr, const GGUFModel & gf, ggml_backend_t backend) {
|
||||
tr->hidden_size = (int) gf_get_u32(gf, "qwen3-tts-tokenizer.encoder.hidden_size");
|
||||
tr->num_layers = (int) gf_get_u32(gf, "qwen3-tts-tokenizer.encoder.num_hidden_layers");
|
||||
tr->num_attention_heads = (int) gf_get_u32(gf, "qwen3-tts-tokenizer.encoder.num_attention_heads");
|
||||
@@ -79,9 +79,9 @@ static bool qwen_encoder_transformer_load(QwenEncoderTransformer * tr, const GGU
|
||||
tr->rope_theta = gf_get_f32(gf, "qwen3-tts-tokenizer.encoder.rope_theta");
|
||||
tr->norm_eps = gf_get_f32(gf, "qwen3-tts-tokenizer.encoder.norm_eps");
|
||||
|
||||
if (tr->num_layers > QWEN_ENCODER_TRANSFORMER_MAX_LAYERS) {
|
||||
if (tr->num_layers > ENC_TRANS_MAX_LAYERS) {
|
||||
fprintf(stderr, "[EncTransformer] FATAL: %d layers exceeds compile-time max %d\n", tr->num_layers,
|
||||
QWEN_ENCODER_TRANSFORMER_MAX_LAYERS);
|
||||
ENC_TRANS_MAX_LAYERS);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -124,7 +124,7 @@ static bool qwen_encoder_transformer_load(QwenEncoderTransformer * tr, const GGU
|
||||
return true;
|
||||
}
|
||||
|
||||
static void qwen_encoder_transformer_free(QwenEncoderTransformer * tr) {
|
||||
static void enc_trans_free(QwenEncoderTransformer * tr) {
|
||||
if (tr->weight_buf) {
|
||||
ggml_backend_buffer_free(tr->weight_buf);
|
||||
tr->weight_buf = NULL;
|
||||
@@ -140,7 +140,7 @@ static void qwen_encoder_transformer_free(QwenEncoderTransformer * tr) {
|
||||
// value but neither MimiAttention's eager forward nor MimiTransformerModel
|
||||
// (create_causal_mask) ever apply it. The Qwen3TTS encoder inherits this
|
||||
// convention, so we mirror it bit for bit here.
|
||||
static void qwen_encoder_build_causal_mask(int T, std::vector<float> & dst) {
|
||||
static void enc_trans_build_causal_mask(int T, std::vector<float> & dst) {
|
||||
dst.assign((size_t) T * (size_t) T, -INFINITY);
|
||||
for (int q = 0; q < T; q++) {
|
||||
for (int k = 0; k <= q; k++) {
|
||||
@@ -149,7 +149,7 @@ static void qwen_encoder_build_causal_mask(int T, std::vector<float> & dst) {
|
||||
}
|
||||
}
|
||||
|
||||
static void qwen_encoder_build_positions(int T, std::vector<int32_t> & dst) {
|
||||
static void enc_trans_build_positions(int T, std::vector<int32_t> & dst) {
|
||||
dst.resize((size_t) T);
|
||||
for (int i = 0; i < T; i++) {
|
||||
dst[i] = i;
|
||||
@@ -163,13 +163,13 @@ static void qwen_encoder_build_positions(int T, std::vector<int32_t> & dst) {
|
||||
// positions: [T] i32
|
||||
// mask : [T, T] f32 additive
|
||||
// Returns [hidden, T] f32 C-first.
|
||||
static struct ggml_tensor * qwen_encoder_transformer_layer_forward(struct ggml_context * ctx,
|
||||
const QwenEncoderTransformer * tr,
|
||||
const QwenEncoderTransformerLayer & layer,
|
||||
struct ggml_tensor * x,
|
||||
struct ggml_tensor * positions,
|
||||
struct ggml_tensor * mask,
|
||||
int T) {
|
||||
static struct ggml_tensor * enc_trans_layer_forward(struct ggml_context * ctx,
|
||||
const QwenEncoderTransformer * tr,
|
||||
const QwenEncoderTransformerLayer & layer,
|
||||
struct ggml_tensor * x,
|
||||
struct ggml_tensor * positions,
|
||||
struct ggml_tensor * mask,
|
||||
int T) {
|
||||
int hidden = tr->hidden_size;
|
||||
int n_q_heads = tr->num_attention_heads;
|
||||
int n_kv = tr->num_kv_heads;
|
||||
@@ -233,14 +233,14 @@ static struct ggml_tensor * qwen_encoder_transformer_layer_forward(struct ggml_c
|
||||
// positions: [T] i32
|
||||
// mask : [T, T] f32 additive
|
||||
// Returns [hidden, T] f32 C-first.
|
||||
static struct ggml_tensor * qwen_encoder_transformer_forward(struct ggml_context * ctx,
|
||||
const QwenEncoderTransformer * tr,
|
||||
struct ggml_tensor * x,
|
||||
struct ggml_tensor * positions,
|
||||
struct ggml_tensor * mask) {
|
||||
static struct ggml_tensor * enc_trans_forward(struct ggml_context * ctx,
|
||||
const QwenEncoderTransformer * tr,
|
||||
struct ggml_tensor * x,
|
||||
struct ggml_tensor * positions,
|
||||
struct ggml_tensor * mask) {
|
||||
int T = (int) x->ne[1];
|
||||
for (int l = 0; l < tr->num_layers; l++) {
|
||||
x = qwen_encoder_transformer_layer_forward(ctx, tr, tr->layers[l], x, positions, mask, T);
|
||||
x = enc_trans_layer_forward(ctx, tr, tr->layers[l], x, positions, mask, T);
|
||||
}
|
||||
return x;
|
||||
}
|
||||
|
||||
+79
-80
@@ -28,28 +28,28 @@ bool pipeline_codec_load(PipelineCodec * pc, const char * gguf_path, BackendPair
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!qwen_quantizer_decoder_load(&pc->qdec, pc->gguf, pc->backend)) {
|
||||
if (!quant_decoder_load(&pc->qdec, pc->gguf, pc->backend)) {
|
||||
gf_close(&pc->gguf);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!qwen_tokenizer_transformer_load(&pc->transformer, pc->gguf, pc->backend)) {
|
||||
qwen_quantizer_decoder_free(&pc->qdec);
|
||||
if (!tok_trans_load(&pc->transformer, pc->gguf, pc->backend)) {
|
||||
quant_decoder_free(&pc->qdec);
|
||||
gf_close(&pc->gguf);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!qwen_upsample_stage_load(&pc->upsample, pc->gguf, pc->backend)) {
|
||||
qwen_tokenizer_transformer_free(&pc->transformer);
|
||||
qwen_quantizer_decoder_free(&pc->qdec);
|
||||
if (!upsample_stage_load(&pc->upsample, pc->gguf, pc->backend)) {
|
||||
tok_trans_free(&pc->transformer);
|
||||
quant_decoder_free(&pc->qdec);
|
||||
gf_close(&pc->gguf);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!qwen_dac_decoder_load(&pc->dac, pc->gguf, pc->backend)) {
|
||||
qwen_upsample_stage_free(&pc->upsample);
|
||||
qwen_tokenizer_transformer_free(&pc->transformer);
|
||||
qwen_quantizer_decoder_free(&pc->qdec);
|
||||
if (!dac_decoder_load(&pc->dac, pc->gguf, pc->backend)) {
|
||||
upsample_stage_free(&pc->upsample);
|
||||
tok_trans_free(&pc->transformer);
|
||||
quant_decoder_free(&pc->qdec);
|
||||
gf_close(&pc->gguf);
|
||||
return false;
|
||||
}
|
||||
@@ -62,10 +62,10 @@ bool pipeline_codec_load(PipelineCodec * pc, const char * gguf_path, BackendPair
|
||||
pc->pre_conv_b = gf_load_tensor(&wctx, pc->gguf, "tok_dec.pre_conv.bias");
|
||||
if (!wctx_alloc(&wctx, pc->backend)) {
|
||||
qt_log(QT_LOG_ERROR, "[Pipeline] pre_conv backend allocation failed");
|
||||
qwen_dac_decoder_free(&pc->dac);
|
||||
qwen_upsample_stage_free(&pc->upsample);
|
||||
qwen_tokenizer_transformer_free(&pc->transformer);
|
||||
qwen_quantizer_decoder_free(&pc->qdec);
|
||||
dac_decoder_free(&pc->dac);
|
||||
upsample_stage_free(&pc->upsample);
|
||||
tok_trans_free(&pc->transformer);
|
||||
quant_decoder_free(&pc->qdec);
|
||||
gf_close(&pc->gguf);
|
||||
return false;
|
||||
}
|
||||
@@ -73,66 +73,66 @@ bool pipeline_codec_load(PipelineCodec * pc, const char * gguf_path, BackendPair
|
||||
pc->pre_conv_buf = wctx.buffer;
|
||||
}
|
||||
|
||||
if (!qwen_seanet_encoder_load(&pc->seanet, pc->gguf, pc->backend)) {
|
||||
if (!seanet_encoder_load(&pc->seanet, pc->gguf, pc->backend)) {
|
||||
ggml_backend_buffer_free(pc->pre_conv_buf);
|
||||
ggml_free(pc->pre_conv_ctx);
|
||||
qwen_dac_decoder_free(&pc->dac);
|
||||
qwen_upsample_stage_free(&pc->upsample);
|
||||
qwen_tokenizer_transformer_free(&pc->transformer);
|
||||
qwen_quantizer_decoder_free(&pc->qdec);
|
||||
dac_decoder_free(&pc->dac);
|
||||
upsample_stage_free(&pc->upsample);
|
||||
tok_trans_free(&pc->transformer);
|
||||
quant_decoder_free(&pc->qdec);
|
||||
gf_close(&pc->gguf);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!qwen_encoder_transformer_load(&pc->enc_transformer, pc->gguf, pc->backend)) {
|
||||
qwen_seanet_encoder_free(&pc->seanet);
|
||||
if (!enc_trans_load(&pc->enc_transformer, pc->gguf, pc->backend)) {
|
||||
seanet_encoder_free(&pc->seanet);
|
||||
ggml_backend_buffer_free(pc->pre_conv_buf);
|
||||
ggml_free(pc->pre_conv_ctx);
|
||||
qwen_dac_decoder_free(&pc->dac);
|
||||
qwen_upsample_stage_free(&pc->upsample);
|
||||
qwen_tokenizer_transformer_free(&pc->transformer);
|
||||
qwen_quantizer_decoder_free(&pc->qdec);
|
||||
dac_decoder_free(&pc->dac);
|
||||
upsample_stage_free(&pc->upsample);
|
||||
tok_trans_free(&pc->transformer);
|
||||
quant_decoder_free(&pc->qdec);
|
||||
gf_close(&pc->gguf);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!qwen_encoder_downsample_load(&pc->enc_downsample, pc->gguf, pc->backend)) {
|
||||
qwen_encoder_transformer_free(&pc->enc_transformer);
|
||||
qwen_seanet_encoder_free(&pc->seanet);
|
||||
if (!enc_down_load(&pc->enc_downsample, pc->gguf, pc->backend)) {
|
||||
enc_trans_free(&pc->enc_transformer);
|
||||
seanet_encoder_free(&pc->seanet);
|
||||
ggml_backend_buffer_free(pc->pre_conv_buf);
|
||||
ggml_free(pc->pre_conv_ctx);
|
||||
qwen_dac_decoder_free(&pc->dac);
|
||||
qwen_upsample_stage_free(&pc->upsample);
|
||||
qwen_tokenizer_transformer_free(&pc->transformer);
|
||||
qwen_quantizer_decoder_free(&pc->qdec);
|
||||
dac_decoder_free(&pc->dac);
|
||||
upsample_stage_free(&pc->upsample);
|
||||
tok_trans_free(&pc->transformer);
|
||||
quant_decoder_free(&pc->qdec);
|
||||
gf_close(&pc->gguf);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!qwen_quantizer_encode_load(&pc->qenc, pc->gguf, pc->backend)) {
|
||||
qwen_encoder_downsample_free(&pc->enc_downsample);
|
||||
qwen_encoder_transformer_free(&pc->enc_transformer);
|
||||
qwen_seanet_encoder_free(&pc->seanet);
|
||||
if (!quant_encode_load(&pc->qenc, pc->gguf, pc->backend)) {
|
||||
enc_down_free(&pc->enc_downsample);
|
||||
enc_trans_free(&pc->enc_transformer);
|
||||
seanet_encoder_free(&pc->seanet);
|
||||
ggml_backend_buffer_free(pc->pre_conv_buf);
|
||||
ggml_free(pc->pre_conv_ctx);
|
||||
qwen_dac_decoder_free(&pc->dac);
|
||||
qwen_upsample_stage_free(&pc->upsample);
|
||||
qwen_tokenizer_transformer_free(&pc->transformer);
|
||||
qwen_quantizer_decoder_free(&pc->qdec);
|
||||
dac_decoder_free(&pc->dac);
|
||||
upsample_stage_free(&pc->upsample);
|
||||
tok_trans_free(&pc->transformer);
|
||||
quant_decoder_free(&pc->qdec);
|
||||
gf_close(&pc->gguf);
|
||||
return false;
|
||||
}
|
||||
|
||||
pc->sched = backend_sched_new(bp, 4096);
|
||||
|
||||
qt_log(QT_LOG_INFO, "[Pipeline] Ready: hop %d samples @ %d Hz mono, %d codebooks @ 12.5 Hz",
|
||||
QWEN_TOKENIZER_HOP_LENGTH, QWEN_TOKENIZER_SAMPLE_RATE, QWEN_TOKENIZER_NUM_CODEBOOKS);
|
||||
qt_log(QT_LOG_INFO, "[Pipeline] Ready: hop %d samples @ %d Hz mono, %d codebooks @ 12.5 Hz", TOKENIZER_HOP_LENGTH,
|
||||
TOKENIZER_SAMPLE_RATE, TOKENIZER_NUM_CODEBOOKS);
|
||||
return true;
|
||||
}
|
||||
|
||||
std::vector<float> pipeline_codec_decode(PipelineCodec * pc, const int32_t * codes, int K, int T) {
|
||||
if (K != QWEN_TOKENIZER_NUM_CODEBOOKS) {
|
||||
qt_log(QT_LOG_ERROR, "[Pipeline] codes have %d codebooks, expected %d", K, QWEN_TOKENIZER_NUM_CODEBOOKS);
|
||||
if (K != TOKENIZER_NUM_CODEBOOKS) {
|
||||
qt_log(QT_LOG_ERROR, "[Pipeline] codes have %d codebooks, expected %d", K, TOKENIZER_NUM_CODEBOOKS);
|
||||
return {};
|
||||
}
|
||||
if (T <= 0) {
|
||||
@@ -168,15 +168,15 @@ std::vector<float> pipeline_codec_decode(PipelineCodec * pc, const int32_t * cod
|
||||
|
||||
// Build forward graph. Layout transitions are explicit ggml_cont(ggml_transpose(...))
|
||||
// calls: 3 transposes total at the natural module boundaries.
|
||||
struct ggml_tensor * h = qwen_quantizer_decode(gctx, &pc->qdec, codes_in); // [512, T] C-first
|
||||
struct ggml_tensor * h = quant_decode(gctx, &pc->qdec, codes_in); // [512, T] C-first
|
||||
h = ggml_cont(gctx, ggml_transpose(gctx, h)); // [T, 512] T-first
|
||||
h = qwen_causal_conv1d(gctx, pc->pre_conv_w, pc->pre_conv_b, h, 3, 1); // [T, 1024] T-first
|
||||
h = ggml_cont(gctx, ggml_transpose(gctx, h)); // [1024, T] C-first
|
||||
h = qwen_tokenizer_transformer_forward(gctx, &pc->transformer, h, positions, mask); // [1024, T]
|
||||
h = ggml_cont(gctx, ggml_transpose(gctx, h)); // [T, 1024] T-first
|
||||
h = qwen_upsample_stage_forward(gctx, &pc->upsample, h); // [T*4, 1024]
|
||||
h = qwen_dac_decoder_forward(gctx, &pc->dac, h); // [T*1920, 1]
|
||||
h = ggml_clamp(gctx, h, -1.0f, 1.0f);
|
||||
h = tok_trans_forward(gctx, &pc->transformer, h, positions, mask); // [1024, T]
|
||||
h = ggml_cont(gctx, ggml_transpose(gctx, h)); // [T, 1024] T-first
|
||||
h = upsample_stage_forward(gctx, &pc->upsample, h); // [T*4, 1024]
|
||||
h = dac_decoder_forward(gctx, &pc->dac, h); // [T*1920, 1]
|
||||
h = ggml_clamp(gctx, h, -1.0f, 1.0f);
|
||||
|
||||
ggml_set_name(h, "audio_out");
|
||||
ggml_set_output(h);
|
||||
@@ -195,11 +195,11 @@ std::vector<float> pipeline_codec_decode(PipelineCodec * pc, const int32_t * cod
|
||||
ggml_backend_tensor_set(codes_in, codes, 0, (size_t) T * (size_t) K * sizeof(int32_t));
|
||||
|
||||
std::vector<int32_t> pos_buf;
|
||||
qwen_build_positions(T, pos_buf);
|
||||
tok_trans_build_positions(T, pos_buf);
|
||||
ggml_backend_tensor_set(positions, pos_buf.data(), 0, pos_buf.size() * sizeof(int32_t));
|
||||
|
||||
std::vector<float> mask_buf;
|
||||
qwen_build_causal_sliding_mask(T, pc->transformer.sliding_window, mask_buf);
|
||||
tok_trans_build_causal_sliding_mask(T, pc->transformer.sliding_window, mask_buf);
|
||||
ggml_backend_tensor_set(mask, mask_buf.data(), 0, mask_buf.size() * sizeof(float));
|
||||
|
||||
// Compute
|
||||
@@ -212,7 +212,7 @@ std::vector<float> pipeline_codec_decode(PipelineCodec * pc, const int32_t * cod
|
||||
}
|
||||
|
||||
// Fetch audio output
|
||||
const int n_samples = T * QWEN_TOKENIZER_HOP_LENGTH;
|
||||
const int n_samples = T * TOKENIZER_HOP_LENGTH;
|
||||
std::vector<float> audio((size_t) n_samples);
|
||||
ggml_backend_tensor_get(h, audio.data(), 0, (size_t) n_samples * sizeof(float));
|
||||
|
||||
@@ -225,19 +225,19 @@ std::vector<int32_t> pipeline_codec_encode(PipelineCodec * pc,
|
||||
const float * audio,
|
||||
int n_samples,
|
||||
const char * dump_dir) {
|
||||
if (n_samples <= 0 || (n_samples % QWEN_TOKENIZER_HOP_LENGTH) != 0) {
|
||||
qt_log(QT_LOG_ERROR, "[Pipeline] n_samples must be a positive multiple of %d (got %d)",
|
||||
QWEN_TOKENIZER_HOP_LENGTH, n_samples);
|
||||
if (n_samples <= 0 || (n_samples % TOKENIZER_HOP_LENGTH) != 0) {
|
||||
qt_log(QT_LOG_ERROR, "[Pipeline] n_samples must be a positive multiple of %d (got %d)", TOKENIZER_HOP_LENGTH,
|
||||
n_samples);
|
||||
return {};
|
||||
}
|
||||
int T = n_samples / QWEN_TOKENIZER_HOP_LENGTH;
|
||||
int T = n_samples / TOKENIZER_HOP_LENGTH;
|
||||
|
||||
// Lazy-load CPU mirror of the RVQ encode codebooks on first call.
|
||||
if (!pc->qenc_host_ready) {
|
||||
qwen_quantizer_encode_host_load(&pc->qenc_sem_host, pc->qenc.semantic, pc->qenc.codebook_size,
|
||||
pc->qenc.codebook_dim, pc->qenc.hidden_size);
|
||||
qwen_quantizer_encode_host_load(&pc->qenc_aco_host, pc->qenc.acoustic, pc->qenc.codebook_size,
|
||||
pc->qenc.codebook_dim, pc->qenc.hidden_size);
|
||||
quant_encode_host_load(&pc->qenc_sem_host, pc->qenc.semantic, pc->qenc.codebook_size, pc->qenc.codebook_dim,
|
||||
pc->qenc.hidden_size);
|
||||
quant_encode_host_load(&pc->qenc_aco_host, pc->qenc.acoustic, pc->qenc.codebook_size, pc->qenc.codebook_dim,
|
||||
pc->qenc.hidden_size);
|
||||
pc->qenc_host_ready = true;
|
||||
}
|
||||
|
||||
@@ -274,13 +274,12 @@ std::vector<int32_t> pipeline_codec_encode(PipelineCodec * pc,
|
||||
struct ggml_tensor * sn_stage1_t = NULL;
|
||||
struct ggml_tensor * sn_stage3_t = NULL;
|
||||
struct ggml_tensor * h_seanet =
|
||||
qwen_seanet_encoder_forward(gctx, &pc->seanet, audio_in, &sn_init_t, &sn_resnet0_t, &sn_stage0_t, &sn_stage1_t,
|
||||
&sn_stage3_t); // [T_emb, 512]
|
||||
struct ggml_tensor * h = ggml_cont(gctx, ggml_transpose(gctx, h_seanet)); // [512, T_emb]
|
||||
struct ggml_tensor * h_et =
|
||||
qwen_encoder_transformer_forward(gctx, &pc->enc_transformer, h, positions, mask); // [512, T_emb]
|
||||
h = ggml_cont(gctx, ggml_transpose(gctx, h_et)); // [T_emb, 512]
|
||||
h = qwen_encoder_downsample_forward(gctx, &pc->enc_downsample, h); // [T, 512]
|
||||
seanet_encoder_forward(gctx, &pc->seanet, audio_in, &sn_init_t, &sn_resnet0_t, &sn_stage0_t, &sn_stage1_t,
|
||||
&sn_stage3_t); // [T_emb, 512]
|
||||
struct ggml_tensor * h = ggml_cont(gctx, ggml_transpose(gctx, h_seanet)); // [512, T_emb]
|
||||
struct ggml_tensor * h_et = enc_trans_forward(gctx, &pc->enc_transformer, h, positions, mask); // [512, T_emb]
|
||||
h = ggml_cont(gctx, ggml_transpose(gctx, h_et)); // [T_emb, 512]
|
||||
h = enc_down_forward(gctx, &pc->enc_downsample, h); // [T, 512]
|
||||
|
||||
// The CPU RVQ encode loop expects the hidden buffer as [T, hidden]
|
||||
// row-major (hidden fast in memory). The downsample output ne=(T, 512)
|
||||
@@ -369,11 +368,11 @@ std::vector<int32_t> pipeline_codec_encode(PipelineCodec * pc,
|
||||
ggml_backend_tensor_set(audio_in, audio, 0, (size_t) n_samples * sizeof(float));
|
||||
|
||||
std::vector<int32_t> pos_buf;
|
||||
qwen_encoder_build_positions(T_emb, pos_buf);
|
||||
enc_trans_build_positions(T_emb, pos_buf);
|
||||
ggml_backend_tensor_set(positions, pos_buf.data(), 0, pos_buf.size() * sizeof(int32_t));
|
||||
|
||||
std::vector<float> mask_buf;
|
||||
qwen_encoder_build_causal_mask(T_emb, mask_buf);
|
||||
enc_trans_build_causal_mask(T_emb, mask_buf);
|
||||
ggml_backend_tensor_set(mask, mask_buf.data(), 0, mask_buf.size() * sizeof(float));
|
||||
|
||||
enum ggml_status st = ggml_backend_sched_graph_compute(pc->sched, graph);
|
||||
@@ -424,14 +423,14 @@ std::vector<int32_t> pipeline_codec_encode(PipelineCodec * pc,
|
||||
// Read back the post-downsample hidden buffer for CPU-side RVQ encode.
|
||||
// Layout in ggml is [T, hidden] with T on ne[0]. The contiguous memory
|
||||
// walks T fast, hidden slow, which matches the `[T, hidden] row-major
|
||||
// index = t*hidden + c` convention expected by qwen_quantizer_encode_cpu.
|
||||
// index = t*hidden + c` convention expected by quant_encode_cpu.
|
||||
std::vector<float> hidden_host((size_t) T * (size_t) pc->qenc.hidden_size);
|
||||
ggml_backend_tensor_get(h, hidden_host.data(), 0, hidden_host.size() * sizeof(float));
|
||||
|
||||
ggml_backend_sched_reset(pc->sched);
|
||||
ggml_free(gctx);
|
||||
|
||||
return qwen_quantizer_encode_cpu(&pc->qenc_sem_host, &pc->qenc_aco_host, hidden_host.data(), T);
|
||||
return quant_encode_cpu(&pc->qenc_sem_host, &pc->qenc_aco_host, hidden_host.data(), T);
|
||||
}
|
||||
|
||||
void pipeline_codec_free(PipelineCodec * pc) {
|
||||
@@ -439,10 +438,10 @@ void pipeline_codec_free(PipelineCodec * pc) {
|
||||
ggml_backend_sched_free(pc->sched);
|
||||
pc->sched = NULL;
|
||||
}
|
||||
qwen_quantizer_encode_free(&pc->qenc);
|
||||
qwen_encoder_downsample_free(&pc->enc_downsample);
|
||||
qwen_encoder_transformer_free(&pc->enc_transformer);
|
||||
qwen_seanet_encoder_free(&pc->seanet);
|
||||
quant_encode_free(&pc->qenc);
|
||||
enc_down_free(&pc->enc_downsample);
|
||||
enc_trans_free(&pc->enc_transformer);
|
||||
seanet_encoder_free(&pc->seanet);
|
||||
if (pc->pre_conv_buf) {
|
||||
ggml_backend_buffer_free(pc->pre_conv_buf);
|
||||
pc->pre_conv_buf = NULL;
|
||||
@@ -451,10 +450,10 @@ void pipeline_codec_free(PipelineCodec * pc) {
|
||||
ggml_free(pc->pre_conv_ctx);
|
||||
pc->pre_conv_ctx = NULL;
|
||||
}
|
||||
qwen_dac_decoder_free(&pc->dac);
|
||||
qwen_upsample_stage_free(&pc->upsample);
|
||||
qwen_tokenizer_transformer_free(&pc->transformer);
|
||||
qwen_quantizer_decoder_free(&pc->qdec);
|
||||
dac_decoder_free(&pc->dac);
|
||||
upsample_stage_free(&pc->upsample);
|
||||
tok_trans_free(&pc->transformer);
|
||||
quant_decoder_free(&pc->qdec);
|
||||
if (pc->gguf.gguf) {
|
||||
gf_close(&pc->gguf);
|
||||
}
|
||||
|
||||
@@ -39,10 +39,10 @@
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
#define QWEN_TOKENIZER_HOP_LENGTH 1920
|
||||
#define QWEN_TOKENIZER_SAMPLE_RATE 24000
|
||||
#define QWEN_TOKENIZER_NUM_CODEBOOKS 16
|
||||
#define QWEN_TOKENIZER_CODE_BITS 11
|
||||
#define TOKENIZER_HOP_LENGTH 1920
|
||||
#define TOKENIZER_SAMPLE_RATE 24000
|
||||
#define TOKENIZER_NUM_CODEBOOKS 16
|
||||
#define TOKENIZER_CODE_BITS 11
|
||||
|
||||
struct PipelineCodec {
|
||||
GGUFModel gguf;
|
||||
@@ -82,18 +82,18 @@ bool pipeline_codec_load(PipelineCodec * pc, const char * gguf_path, BackendPair
|
||||
|
||||
// Decode RVQ codes into a 24 kHz mono waveform.
|
||||
// codes: flat int32 buffer, [K, T] row-major (T fastest).
|
||||
// Returns audio of length T * QWEN_TOKENIZER_HOP_LENGTH, empty on failure.
|
||||
// Returns audio of length T * TOKENIZER_HOP_LENGTH, empty on failure.
|
||||
std::vector<float> pipeline_codec_decode(PipelineCodec * pc, const int32_t * codes, int K, int T);
|
||||
|
||||
// Encode a 24 kHz mono waveform into RVQ codes.
|
||||
// audio : [n_samples] f32 mono 24 kHz. Must be a multiple of
|
||||
// QWEN_TOKENIZER_HOP_LENGTH (1920); the caller is expected
|
||||
// TOKENIZER_HOP_LENGTH (1920); the caller is expected
|
||||
// to pad with zeros if needed.
|
||||
// dump_dir: optional path. When non NULL, dumps the SEANet, encoder
|
||||
// transformer and post-downsample (pre-FSQ latents) buffers
|
||||
// into seanet-out.bin, enc-transformer-out.bin and
|
||||
// codec-pre-fsq.bin under that directory. Quiet otherwise.
|
||||
// Returns codes flat as [K, T] row-major, K = QWEN_TOKENIZER_NUM_CODEBOOKS,
|
||||
// Returns codes flat as [K, T] row-major, K = TOKENIZER_NUM_CODEBOOKS,
|
||||
// T = n_samples / 1920. Empty on failure.
|
||||
std::vector<int32_t> pipeline_codec_encode(PipelineCodec * pc,
|
||||
const float * audio,
|
||||
|
||||
@@ -272,7 +272,7 @@ bool pipeline_tts_synthesize(PipelineTTS * pt,
|
||||
const PipelineTTSSynthesizeParams & params,
|
||||
PipelineTTSSynthesizeOutput * out) {
|
||||
out->audio.clear();
|
||||
out->sample_rate = QWEN_TOKENIZER_SAMPLE_RATE;
|
||||
out->sample_rate = TOKENIZER_SAMPLE_RATE;
|
||||
|
||||
PromptBuilderOutput prompt;
|
||||
const std::string instruct = params.instruct ? params.instruct : "";
|
||||
@@ -322,12 +322,12 @@ bool pipeline_tts_synthesize(PipelineTTS * pt,
|
||||
}
|
||||
// The codec hop is 1920 samples at 24 kHz so n_samples must be
|
||||
// a multiple of 1920. Truncate to the nearest hop boundary.
|
||||
if (params.ref_n_samples < QWEN_TOKENIZER_HOP_LENGTH) {
|
||||
if (params.ref_n_samples < TOKENIZER_HOP_LENGTH) {
|
||||
qt_set_error("pipeline_tts_synthesize: ref_wav too short for ICL (%d samples)", params.ref_n_samples);
|
||||
qt_log(QT_LOG_ERROR, "[Pipeline] ref_wav too short for ICL (%d samples)", params.ref_n_samples);
|
||||
return false;
|
||||
}
|
||||
int aligned_T = (params.ref_n_samples / QWEN_TOKENIZER_HOP_LENGTH) * QWEN_TOKENIZER_HOP_LENGTH;
|
||||
int aligned_T = (params.ref_n_samples / TOKENIZER_HOP_LENGTH) * TOKENIZER_HOP_LENGTH;
|
||||
ref_codes = pipeline_codec_encode(&pt->codec, params.ref_audio_24k, aligned_T, params.dump_dir);
|
||||
if (ref_codes.empty()) {
|
||||
qt_set_error("pipeline_tts_synthesize: pipeline_codec_encode returned empty codes");
|
||||
|
||||
+26
-44
@@ -1,48 +1,33 @@
|
||||
#pragma once
|
||||
// qt-error.h: internal helpers backing the public qwen_last_error
|
||||
// entry and the qwen_log callback routing.
|
||||
// qt-error.h: internal helpers backing the public qt_last_error() entry
|
||||
// and the qt_log_set callback routing.
|
||||
//
|
||||
// Not part of the public ABI. Translation units that emit user-facing
|
||||
// errors include this header to record a diagnostic on the calling
|
||||
// thread before they return a negative qwen_status (or false). The
|
||||
// actual storage and the public qwen_last_error() reader live in
|
||||
// qwen.cpp.
|
||||
// errors include this header to record a diagnostic on the calling thread
|
||||
// before they return a negative qt_status (or NULL). The actual storage
|
||||
// and the public qt_last_error() reader live in qwen.cpp.
|
||||
//
|
||||
// Storage is thread_local so concurrent qwen_synthesize calls on
|
||||
// different threads never race on each other's messages. The setter is
|
||||
// variadic with printf semantics; messages longer than the internal
|
||||
// buffer are truncated, never split. Passing NULL as fmt clears the
|
||||
// slot.
|
||||
// Storage is thread_local so concurrent qt_synthesize calls on different
|
||||
// threads never race on each other's messages. The setter is variadic with
|
||||
// printf semantics; messages longer than the internal buffer are
|
||||
// truncated, never split. Passing NULL as fmt clears the slot.
|
||||
//
|
||||
// qt_throw is the load-path counterpart: functions deep inside the
|
||||
// GGUF reader and the codec load chain cannot return false up dozens
|
||||
// of call sites without a massive cascade. They throw a
|
||||
// std::runtime_error instead, which the ABI boundary entries
|
||||
// (qwen_init, qwen_synthesize) catch and convert into qt_set_error
|
||||
// plus a negative qwen_status. Exceptions never cross any future C ABI.
|
||||
// qt_throw is the load-path counterpart: functions deep inside the GGUF
|
||||
// reader and the codec load chain cannot return false up dozens of call
|
||||
// sites without a massive cascade. They throw a std::runtime_error
|
||||
// instead, which the ABI boundary entries (qt_init, qt_synthesize) catch
|
||||
// and convert into qt_set_error + a negative qt_status. Exceptions never
|
||||
// cross the extern "C" boundary, so the public API stays pure C.
|
||||
//
|
||||
// qt_log routes a formatted message to the user-installed qwen_log_cb,
|
||||
// or to stderr when no callback is installed. Used by every translation
|
||||
// unit in the lib that wants its diagnostics to be redirectable from a
|
||||
// wrapper (Python logging, Rust tracing, ...). The level enum is the
|
||||
// public qwen_log_level, re-exported here under the historic qt_log_level
|
||||
// name so existing call sites stay pixel perfect.
|
||||
// qt_log routes a formatted message to the user-installed qt_log_cb, or
|
||||
// to stderr when no callback is installed. Used by every translation unit
|
||||
// in the lib that wants its diagnostics to be redirectable from a wrapper
|
||||
// (Python logging, Rust tracing, ...). The level enum lives in qwen.h.
|
||||
|
||||
#include "qwen.h"
|
||||
|
||||
#include <cstdarg>
|
||||
|
||||
// Internal log level alias. Same values, same layout as the public
|
||||
// qwen_log_level enum: a single underlying type means a single log
|
||||
// callback installed through qwen_log_set routes every diagnostic
|
||||
// without any cast or translation.
|
||||
typedef enum qwen_log_level qt_log_level;
|
||||
|
||||
#define QT_LOG_DEBUG QWEN_LOG_DEBUG
|
||||
#define QT_LOG_INFO QWEN_LOG_INFO
|
||||
#define QT_LOG_WARN QWEN_LOG_WARN
|
||||
#define QT_LOG_ERROR QWEN_LOG_ERROR
|
||||
|
||||
void qt_set_error(const char * fmt, ...)
|
||||
#if defined(__GNUC__) || defined(__clang__)
|
||||
__attribute__((format(printf, 1, 2)))
|
||||
@@ -54,8 +39,8 @@ void qt_set_error_v(const char * fmt, va_list ap);
|
||||
// Throws std::runtime_error formatted with printf semantics. Tagged
|
||||
// noreturn so the compiler can prune unreachable branches at the call
|
||||
// site. Designed for the GGUF / codec load path where any failure means
|
||||
// the model is unusable and unwinding to the boundary is the only sane
|
||||
// recovery.
|
||||
// the model is unusable and unwinding to the ABI boundary is the only
|
||||
// sane recovery.
|
||||
[[noreturn]] void qt_throw(const char * fmt, ...)
|
||||
#if defined(__GNUC__) || defined(__clang__)
|
||||
__attribute__((format(printf, 1, 2)))
|
||||
@@ -63,15 +48,12 @@ void qt_set_error_v(const char * fmt, va_list ap);
|
||||
;
|
||||
|
||||
// Routes a formatted message at the requested level to the installed
|
||||
// callback, or to stderr when none is set. The message is the full
|
||||
// line without trailing newline; routing layers add their own framing.
|
||||
void qt_log(qt_log_level level, const char * fmt, ...)
|
||||
// qt_log_cb. Defaults to stderr (with a trailing newline) when no
|
||||
// callback is set, so existing fprintf-style call sites can migrate
|
||||
// one at a time without changing user-visible behaviour. printf
|
||||
// semantics; messages longer than the internal buffer are truncated.
|
||||
void qt_log(enum qt_log_level level, const char * fmt, ...)
|
||||
#if defined(__GNUC__) || defined(__clang__)
|
||||
__attribute__((format(printf, 2, 3)))
|
||||
#endif
|
||||
;
|
||||
|
||||
// Returns the most recent error message recorded on the calling thread.
|
||||
// Returns "" if no error has been set on this thread. The pointer stays
|
||||
// valid until the next qt_set_error call on the same thread.
|
||||
const char * qt_last_error(void);
|
||||
|
||||
+16
-16
@@ -19,12 +19,12 @@
|
||||
#include <cstdlib>
|
||||
#include <string>
|
||||
|
||||
#define QWEN_RVQ_MAX_CODEBOOKS_PER_GROUP 15
|
||||
#define RVQ_MAX_CODEBOOKS_PER_GROUP 15
|
||||
|
||||
struct QwenRVQGroup {
|
||||
int num_codebooks;
|
||||
struct ggml_tensor * embed[QWEN_RVQ_MAX_CODEBOOKS_PER_GROUP]; // each [256, 2048] f32
|
||||
struct ggml_tensor * out_proj_w; // [256, 512] f32 (Conv1d 1x1 reshaped)
|
||||
struct ggml_tensor * embed[RVQ_MAX_CODEBOOKS_PER_GROUP]; // each [256, 2048] f32
|
||||
struct ggml_tensor * out_proj_w; // [256, 512] f32 (Conv1d 1x1 reshaped)
|
||||
};
|
||||
|
||||
struct QwenQuantizerDecoder {
|
||||
@@ -59,7 +59,7 @@ static struct ggml_tensor * qwen_load_proj_1x1(WeightCtx * wctx, const GGUFModel
|
||||
// Build the on-backend weights of the split RVQ decoder from a loaded GGUF.
|
||||
// Mutates dec->weight_ctx and dec->weight_buf, and binds every group
|
||||
// tensor pointer to a backend allocation.
|
||||
static bool qwen_quantizer_decoder_load(QwenQuantizerDecoder * dec, const GGUFModel & gf, ggml_backend_t backend) {
|
||||
static bool quant_decoder_load(QwenQuantizerDecoder * dec, const GGUFModel & gf, ggml_backend_t backend) {
|
||||
dec->num_quantizers = (int) gf_get_u32(gf, "qwen3-tts-tokenizer.decoder.num_quantizers");
|
||||
dec->num_semantic_quantizers = (int) gf_get_u32(gf, "qwen3-tts-tokenizer.decoder.num_semantic_quantizers");
|
||||
dec->num_acoustic_quantizers = dec->num_quantizers - dec->num_semantic_quantizers;
|
||||
@@ -67,9 +67,9 @@ static bool qwen_quantizer_decoder_load(QwenQuantizerDecoder * dec, const GGUFMo
|
||||
dec->codebook_dim_internal = (int) gf_get_u32(gf, "qwen3-tts-tokenizer.decoder.codebook_dim_internal");
|
||||
dec->hidden = (int) gf_get_u32(gf, "qwen3-tts-tokenizer.decoder.vector_quantization_hidden_dim");
|
||||
|
||||
if (dec->num_acoustic_quantizers > QWEN_RVQ_MAX_CODEBOOKS_PER_GROUP) {
|
||||
if (dec->num_acoustic_quantizers > RVQ_MAX_CODEBOOKS_PER_GROUP) {
|
||||
fprintf(stderr, "[Quantizer] FATAL: %d acoustic codebooks exceeds compile-time max %d\n",
|
||||
dec->num_acoustic_quantizers, QWEN_RVQ_MAX_CODEBOOKS_PER_GROUP);
|
||||
dec->num_acoustic_quantizers, RVQ_MAX_CODEBOOKS_PER_GROUP);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -110,7 +110,7 @@ static bool qwen_quantizer_decoder_load(QwenQuantizerDecoder * dec, const GGUFMo
|
||||
return true;
|
||||
}
|
||||
|
||||
static void qwen_quantizer_decoder_free(QwenQuantizerDecoder * dec) {
|
||||
static void quant_decoder_free(QwenQuantizerDecoder * dec) {
|
||||
if (dec->weight_buf) {
|
||||
ggml_backend_buffer_free(dec->weight_buf);
|
||||
dec->weight_buf = NULL;
|
||||
@@ -127,10 +127,10 @@ static void qwen_quantizer_decoder_free(QwenQuantizerDecoder * dec) {
|
||||
//
|
||||
// codes_split: [T, K] i32, K is the codebook count of this split
|
||||
// returns : [hidden, T] f32
|
||||
static struct ggml_tensor * qwen_rvq_group_decode(struct ggml_context * ctx,
|
||||
const QwenRVQGroup & g,
|
||||
struct ggml_tensor * codes_split,
|
||||
int T) {
|
||||
static struct ggml_tensor * rvq_group_decode(struct ggml_context * ctx,
|
||||
const QwenRVQGroup & g,
|
||||
struct ggml_tensor * codes_split,
|
||||
int T) {
|
||||
struct ggml_tensor * sum = NULL;
|
||||
for (int k = 0; k < g.num_codebooks; k++) {
|
||||
struct ggml_tensor * idx = ggml_view_1d(ctx, codes_split, T, (size_t) k * codes_split->nb[1]);
|
||||
@@ -145,9 +145,9 @@ static struct ggml_tensor * qwen_rvq_group_decode(struct ggml_context * ctx,
|
||||
|
||||
// codes: [T, num_quantizers=16] i32
|
||||
// returns: [hidden=512, T] f32
|
||||
static struct ggml_tensor * qwen_quantizer_decode(struct ggml_context * ctx,
|
||||
const QwenQuantizerDecoder * dec,
|
||||
struct ggml_tensor * codes) {
|
||||
static struct ggml_tensor * quant_decode(struct ggml_context * ctx,
|
||||
const QwenQuantizerDecoder * dec,
|
||||
struct ggml_tensor * codes) {
|
||||
int T = (int) codes->ne[0];
|
||||
if ((int) codes->ne[1] != dec->num_quantizers) {
|
||||
fprintf(stderr, "[Quantizer] FATAL: codes ne[1]=%lld != num_quantizers=%d\n", (long long) codes->ne[1],
|
||||
@@ -159,8 +159,8 @@ static struct ggml_tensor * qwen_quantizer_decode(struct ggml_context * c
|
||||
size_t aco_off = (size_t) dec->num_semantic_quantizers * codes->nb[1];
|
||||
struct ggml_tensor * codes_aco = ggml_view_2d(ctx, codes, T, dec->num_acoustic_quantizers, codes->nb[1], aco_off);
|
||||
|
||||
struct ggml_tensor * h_sem = qwen_rvq_group_decode(ctx, dec->semantic, codes_sem, T);
|
||||
struct ggml_tensor * h_aco = qwen_rvq_group_decode(ctx, dec->acoustic, codes_aco, T);
|
||||
struct ggml_tensor * h_sem = rvq_group_decode(ctx, dec->semantic, codes_sem, T);
|
||||
struct ggml_tensor * h_aco = rvq_group_decode(ctx, dec->acoustic, codes_aco, T);
|
||||
|
||||
return ggml_add(ctx, h_sem, h_aco);
|
||||
}
|
||||
|
||||
+35
-35
@@ -33,15 +33,15 @@
|
||||
#include <cstdlib>
|
||||
#include <string>
|
||||
|
||||
#define QWEN_ENC_QUANT_NUM_SEMANTIC 1
|
||||
#define QWEN_ENC_QUANT_NUM_ACOUSTIC 15
|
||||
#define QWEN_ENC_QUANT_TOTAL (QWEN_ENC_QUANT_NUM_SEMANTIC + QWEN_ENC_QUANT_NUM_ACOUSTIC)
|
||||
#define QUANT_ENC_NUM_SEMANTIC 1
|
||||
#define QUANT_ENC_NUM_ACOUSTIC 15
|
||||
#define QUANT_ENC_TOTAL (QUANT_ENC_NUM_SEMANTIC + QUANT_ENC_NUM_ACOUSTIC)
|
||||
|
||||
struct QwenQuantizerEncodeSide {
|
||||
struct ggml_tensor * input_proj_w; // [1, 512, 256] f32, k=1 conv
|
||||
struct ggml_tensor * output_proj_w; // [1, 256, 512] f32, k=1 conv
|
||||
struct ggml_tensor * input_proj_w; // [1, 512, 256] f32, k=1 conv
|
||||
struct ggml_tensor * output_proj_w; // [1, 256, 512] f32, k=1 conv
|
||||
int num_layers;
|
||||
struct ggml_tensor * codebooks[QWEN_ENC_QUANT_NUM_ACOUSTIC]; // [256, 2048] each
|
||||
struct ggml_tensor * codebooks[QUANT_ENC_NUM_ACOUSTIC]; // [256, 2048] each
|
||||
};
|
||||
|
||||
struct QwenQuantizerEncode {
|
||||
@@ -56,28 +56,28 @@ struct QwenQuantizerEncode {
|
||||
ggml_backend_buffer_t weight_buf;
|
||||
};
|
||||
|
||||
static bool qwen_quantizer_encode_load(QwenQuantizerEncode * q, const GGUFModel & gf, ggml_backend_t backend) {
|
||||
static bool quant_encode_load(QwenQuantizerEncode * q, const GGUFModel & gf, ggml_backend_t backend) {
|
||||
q->codebook_size = (int) gf_get_u32(gf, "qwen3-tts-tokenizer.encoder.codebook_size");
|
||||
q->codebook_dim = (int) gf_get_u32(gf, "qwen3-tts-tokenizer.encoder.vector_quantization_hidden_dim");
|
||||
q->hidden_size = (int) gf_get_u32(gf, "qwen3-tts-tokenizer.encoder.hidden_size");
|
||||
|
||||
int n_tensors = 4 + QWEN_ENC_QUANT_TOTAL + 4; // 4 proj + 16 codebooks + headroom
|
||||
int n_tensors = 4 + QUANT_ENC_TOTAL + 4; // 4 proj + 16 codebooks + headroom
|
||||
WeightCtx wctx;
|
||||
wctx_init(&wctx, n_tensors);
|
||||
|
||||
q->semantic.num_layers = QWEN_ENC_QUANT_NUM_SEMANTIC;
|
||||
q->semantic.num_layers = QUANT_ENC_NUM_SEMANTIC;
|
||||
q->semantic.input_proj_w = gf_load_tensor(&wctx, gf, "tok_enc.vq_semantic.input_proj.weight");
|
||||
q->semantic.output_proj_w = gf_load_tensor(&wctx, gf, "tok_enc.vq_semantic.output_proj.weight");
|
||||
for (int i = 0; i < QWEN_ENC_QUANT_NUM_SEMANTIC; i++) {
|
||||
for (int i = 0; i < QUANT_ENC_NUM_SEMANTIC; i++) {
|
||||
char name[96];
|
||||
snprintf(name, sizeof(name), "tok_enc.vq_semantic.%d.codebook", i);
|
||||
q->semantic.codebooks[i] = gf_load_tensor(&wctx, gf, name);
|
||||
}
|
||||
|
||||
q->acoustic.num_layers = QWEN_ENC_QUANT_NUM_ACOUSTIC;
|
||||
q->acoustic.num_layers = QUANT_ENC_NUM_ACOUSTIC;
|
||||
q->acoustic.input_proj_w = gf_load_tensor(&wctx, gf, "tok_enc.vq_acoustic.input_proj.weight");
|
||||
q->acoustic.output_proj_w = gf_load_tensor(&wctx, gf, "tok_enc.vq_acoustic.output_proj.weight");
|
||||
for (int i = 0; i < QWEN_ENC_QUANT_NUM_ACOUSTIC; i++) {
|
||||
for (int i = 0; i < QUANT_ENC_NUM_ACOUSTIC; i++) {
|
||||
char name[96];
|
||||
snprintf(name, sizeof(name), "tok_enc.vq_acoustic.%d.codebook", i);
|
||||
q->acoustic.codebooks[i] = gf_load_tensor(&wctx, gf, name);
|
||||
@@ -93,12 +93,12 @@ static bool qwen_quantizer_encode_load(QwenQuantizerEncode * q, const GGUFModel
|
||||
fprintf(stderr,
|
||||
"[EncQuantizer] Loaded: %d codebooks (%d semantic + %d acoustic), %d entries x %d dim, "
|
||||
"hidden %d, weights %.1f MB\n",
|
||||
QWEN_ENC_QUANT_TOTAL, QWEN_ENC_QUANT_NUM_SEMANTIC, QWEN_ENC_QUANT_NUM_ACOUSTIC, q->codebook_size,
|
||||
q->codebook_dim, q->hidden_size, (float) ggml_backend_buffer_get_size(q->weight_buf) / (1024.0f * 1024.0f));
|
||||
QUANT_ENC_TOTAL, QUANT_ENC_NUM_SEMANTIC, QUANT_ENC_NUM_ACOUSTIC, q->codebook_size, q->codebook_dim,
|
||||
q->hidden_size, (float) ggml_backend_buffer_get_size(q->weight_buf) / (1024.0f * 1024.0f));
|
||||
return true;
|
||||
}
|
||||
|
||||
static void qwen_quantizer_encode_free(QwenQuantizerEncode * q) {
|
||||
static void quant_encode_free(QwenQuantizerEncode * q) {
|
||||
if (q->weight_buf) {
|
||||
ggml_backend_buffer_free(q->weight_buf);
|
||||
q->weight_buf = NULL;
|
||||
@@ -128,11 +128,11 @@ struct QwenQuantizerEncodeHost {
|
||||
std::vector<float> output_proj;
|
||||
};
|
||||
|
||||
static void qwen_quantizer_encode_host_load(QwenQuantizerEncodeHost * h,
|
||||
const QwenQuantizerEncodeSide & side,
|
||||
int codebook_size,
|
||||
int codebook_dim,
|
||||
int hidden_size) {
|
||||
static void quant_encode_host_load(QwenQuantizerEncodeHost * h,
|
||||
const QwenQuantizerEncodeSide & side,
|
||||
int codebook_size,
|
||||
int codebook_dim,
|
||||
int hidden_size) {
|
||||
h->num_layers = side.num_layers;
|
||||
h->codebook_size = codebook_size;
|
||||
h->codebook_dim = codebook_dim;
|
||||
@@ -169,7 +169,7 @@ static void qwen_quantizer_encode_host_load(QwenQuantizerEncodeHost * h,
|
||||
// The contiguous memory of the ggml weight walks `in` fast and `out` slow,
|
||||
// matching the numpy view as [out, in] row-major. So `w[o*in + i]` selects
|
||||
// row o, column i of the underlying [out, in] matrix.
|
||||
static void qwen_quantizer_encode_linear(const float * w, int in_dim, int out_dim, const float * x, int N, float * y) {
|
||||
static void quant_encode_linear(const float * w, int in_dim, int out_dim, const float * x, int N, float * y) {
|
||||
for (int n = 0; n < N; n++) {
|
||||
const float * xn = x + (size_t) n * (size_t) in_dim;
|
||||
float * yn = y + (size_t) n * (size_t) out_dim;
|
||||
@@ -187,10 +187,10 @@ static void qwen_quantizer_encode_linear(const float * w, int in_dim, int out_di
|
||||
// One RVQ side encode loop. Mutates `res` in-place as the residual stream.
|
||||
// Appends T frames of codebook indices for each of side.num_layers, in
|
||||
// the order: layer_0[0..T], layer_1[0..T], ..., layer_{L-1}[0..T].
|
||||
static void qwen_quantizer_encode_side_loop(const QwenQuantizerEncodeHost * h,
|
||||
std::vector<float> & res,
|
||||
int T,
|
||||
std::vector<int32_t> & codes_out) {
|
||||
static void quant_encode_side_loop(const QwenQuantizerEncodeHost * h,
|
||||
std::vector<float> & res,
|
||||
int T,
|
||||
std::vector<int32_t> & codes_out) {
|
||||
int D = h->codebook_dim;
|
||||
int E = h->codebook_size;
|
||||
|
||||
@@ -229,30 +229,30 @@ static void qwen_quantizer_encode_side_loop(const QwenQuantizerEncodeHost * h,
|
||||
|
||||
// Full RVQ encode. Takes the post-downsample hidden [T, hidden_size] f32
|
||||
// row-major buffer and returns flat codes [K, T] row-major, where K is
|
||||
// QWEN_ENC_QUANT_TOTAL = 16.
|
||||
// QUANT_ENC_TOTAL = 16.
|
||||
// hidden: [T, hidden_size] f32 row-major (T fast in pseudo, but here
|
||||
// row-major means index = t*hidden + c, t slow, c fast)
|
||||
//
|
||||
// Returns codes flat as [16, T] row-major: codes[k*T + t].
|
||||
static std::vector<int32_t> qwen_quantizer_encode_cpu(const QwenQuantizerEncodeHost * sem,
|
||||
const QwenQuantizerEncodeHost * aco,
|
||||
const float * hidden,
|
||||
int T) {
|
||||
static std::vector<int32_t> quant_encode_cpu(const QwenQuantizerEncodeHost * sem,
|
||||
const QwenQuantizerEncodeHost * aco,
|
||||
const float * hidden,
|
||||
int T) {
|
||||
std::vector<int32_t> codes;
|
||||
codes.reserve((size_t) QWEN_ENC_QUANT_TOTAL * (size_t) T);
|
||||
codes.reserve((size_t) QUANT_ENC_TOTAL * (size_t) T);
|
||||
|
||||
// Project hidden to codebook_dim for each side independently.
|
||||
int D = sem->codebook_dim;
|
||||
|
||||
// Semantic side
|
||||
std::vector<float> proj_sem((size_t) T * (size_t) D);
|
||||
qwen_quantizer_encode_linear(sem->input_proj.data(), sem->hidden_size, D, hidden, T, proj_sem.data());
|
||||
qwen_quantizer_encode_side_loop(sem, proj_sem, T, codes);
|
||||
quant_encode_linear(sem->input_proj.data(), sem->hidden_size, D, hidden, T, proj_sem.data());
|
||||
quant_encode_side_loop(sem, proj_sem, T, codes);
|
||||
|
||||
// Acoustic side
|
||||
std::vector<float> proj_aco((size_t) T * (size_t) D);
|
||||
qwen_quantizer_encode_linear(aco->input_proj.data(), aco->hidden_size, D, hidden, T, proj_aco.data());
|
||||
qwen_quantizer_encode_side_loop(aco, proj_aco, T, codes);
|
||||
quant_encode_linear(aco->input_proj.data(), aco->hidden_size, D, hidden, T, proj_aco.data());
|
||||
quant_encode_side_loop(aco, proj_aco, T, codes);
|
||||
|
||||
return codes;
|
||||
}
|
||||
|
||||
+71
-79
@@ -3,17 +3,16 @@
|
||||
// Every entry declared in qwen.h lives here under one extern "C" block
|
||||
// so the symbols carry C linkage and are linkable from C, Rust, Go,
|
||||
// Python ctypes and any other binding generator. The struct
|
||||
// qwen_context opaque handle owns one BackendPair, one PipelineTTS
|
||||
// (which embeds its PipelineCodec) and one BPETokenizer. qwen_init
|
||||
// qt_context opaque handle owns one BackendPair, one PipelineTTS
|
||||
// (which embeds its PipelineCodec) and one BPETokenizer. qt_init
|
||||
// walks the load chain in dependency order and unwinds whatever it
|
||||
// already allocated when any step fails. qwen_free mirrors that order
|
||||
// already allocated when any step fails. qt_free mirrors that order
|
||||
// in reverse.
|
||||
//
|
||||
// This translation unit also absorbs the internal qt_set_error /
|
||||
// qt_throw / qt_log helpers that the rest of the codebase calls. The
|
||||
// internal qt_log_level enum is a typedef of the public qwen_log_level
|
||||
// (same values, same layout) so a single log callback installed via
|
||||
// qwen_log_set routes every diagnostic, internal or public.
|
||||
// log callback installed via qt_log_set routes every diagnostic from
|
||||
// any caller, internal or public.
|
||||
|
||||
#include "qwen.h"
|
||||
|
||||
@@ -34,9 +33,9 @@
|
||||
|
||||
// Internal definition of the opaque handle. C++ types are fine here
|
||||
// because nothing in this struct ever crosses the public ABI boundary :
|
||||
// callers only ever see `struct qwen_context *`. PipelineTTS already
|
||||
// callers only ever see `struct qt_context *`. PipelineTTS already
|
||||
// embeds the PipelineCodec, so no separate codec field is needed.
|
||||
struct qwen_context {
|
||||
struct qt_context {
|
||||
BackendPair bp;
|
||||
PipelineTTS pt;
|
||||
BPETokenizer tok;
|
||||
@@ -74,10 +73,6 @@ void qt_set_error(const char * fmt, ...) {
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
const char * qt_last_error(void) {
|
||||
return g_last_error.c_str();
|
||||
}
|
||||
|
||||
// Formats a message with printf semantics and throws std::runtime_error.
|
||||
// The catch site at the binary entry inspects the what() string and feeds
|
||||
// it into qt_set_error so the user-visible diagnostic is identical
|
||||
@@ -95,14 +90,14 @@ void qt_throw(const char * fmt, ...) {
|
||||
throw std::runtime_error(buf);
|
||||
}
|
||||
|
||||
// Process-wide log callback. Atomic so qwen_log_set can replace it without
|
||||
// Process-wide log callback. Atomic so qt_log_set can replace it without
|
||||
// locking: write happens with memory_order_release, every reader sees a
|
||||
// fully published callback pointer paired with its user_data slot.
|
||||
// std::atomic on a function pointer is lock-free on every platform we
|
||||
// target. user_data is a plain pointer because it is only ever published
|
||||
// alongside cb under the same release ordering.
|
||||
static std::atomic<qwen_log_cb> g_log_cb{ nullptr };
|
||||
static void * g_log_cb_user = nullptr;
|
||||
static std::atomic<qt_log_cb> g_log_cb{ nullptr };
|
||||
static void * g_log_cb_user = nullptr;
|
||||
|
||||
// Routes one log line to the installed callback or to stderr. Two-pass
|
||||
// vsnprintf sizes the heap buffer when the message exceeds the stack
|
||||
@@ -134,7 +129,7 @@ void qt_log(qt_log_level level, const char * fmt, ...) {
|
||||
}
|
||||
va_end(ap);
|
||||
|
||||
qwen_log_cb cb = g_log_cb.load(std::memory_order_acquire);
|
||||
qt_log_cb cb = g_log_cb.load(std::memory_order_acquire);
|
||||
if (cb) {
|
||||
cb(level, buf, g_log_cb_user);
|
||||
} else {
|
||||
@@ -144,20 +139,20 @@ void qt_log(qt_log_level level, const char * fmt, ...) {
|
||||
|
||||
extern "C" {
|
||||
|
||||
const char * qwen_version(void) {
|
||||
const char * qt_version(void) {
|
||||
// QWEN_VERSION is a string literal injected by tools/version.cmake
|
||||
// ("<git-hash> (<date>)"), so its storage already has process
|
||||
// lifetime and no formatting wrapper is needed.
|
||||
return QWEN_VERSION;
|
||||
}
|
||||
|
||||
const char * qwen_last_error(void) {
|
||||
const char * qt_last_error(void) {
|
||||
// c_str() on an empty std::string is guaranteed to point to a NUL
|
||||
// byte by C++11, so callers never have to NULL-check the result.
|
||||
return g_last_error.c_str();
|
||||
}
|
||||
|
||||
void qwen_audio_free(struct qwen_audio * a) {
|
||||
void qt_audio_free(struct qt_audio * a) {
|
||||
if (!a) {
|
||||
return;
|
||||
}
|
||||
@@ -170,19 +165,19 @@ void qwen_audio_free(struct qwen_audio * a) {
|
||||
a->channels = 0;
|
||||
}
|
||||
|
||||
void qwen_log_set(qwen_log_cb cb, void * user_data) {
|
||||
void qt_log_set(qt_log_cb cb, void * user_data) {
|
||||
g_log_cb_user = user_data;
|
||||
g_log_cb.store(cb, std::memory_order_release);
|
||||
}
|
||||
|
||||
void qwen_init_default_params(struct qwen_init_params * p) {
|
||||
p->abi_version = QWEN_ABI_VERSION;
|
||||
void qt_init_default_params(struct qt_init_params * p) {
|
||||
p->abi_version = QT_ABI_VERSION;
|
||||
p->talker_path = nullptr;
|
||||
p->codec_path = nullptr;
|
||||
}
|
||||
|
||||
void qwen_tts_default_params(struct qwen_tts_params * p) {
|
||||
p->abi_version = QWEN_ABI_VERSION;
|
||||
void qt_tts_default_params(struct qt_tts_params * p) {
|
||||
p->abi_version = QT_ABI_VERSION;
|
||||
p->text = nullptr;
|
||||
p->lang = "english";
|
||||
p->instruct = nullptr;
|
||||
@@ -204,41 +199,40 @@ void qwen_tts_default_params(struct qwen_tts_params * p) {
|
||||
p->dump_dir = nullptr;
|
||||
}
|
||||
|
||||
struct qwen_context * qwen_init(const struct qwen_init_params * params) {
|
||||
struct qt_context * qt_init(const struct qt_init_params * params) {
|
||||
if (!params || !params->talker_path || !params->codec_path) {
|
||||
qt_set_error("qwen_init: params, talker_path or codec_path is NULL");
|
||||
qt_log(QT_LOG_ERROR, "[Qwen] qwen_init requires talker_path and codec_path");
|
||||
qt_set_error("qt_init: params, talker_path or codec_path is NULL");
|
||||
qt_log(QT_LOG_ERROR, "[Qwen] qt_init requires talker_path and codec_path");
|
||||
return nullptr;
|
||||
}
|
||||
if (params->abi_version > QWEN_ABI_VERSION) {
|
||||
qt_set_error(
|
||||
"qwen_init: params->abi_version %d > QWEN_ABI_VERSION %d (binding compiled against a newer header)",
|
||||
params->abi_version, QWEN_ABI_VERSION);
|
||||
qt_log(QT_LOG_ERROR, "[Qwen] qwen_init params struct is from a newer ABI (%d > %d)", params->abi_version,
|
||||
QWEN_ABI_VERSION);
|
||||
if (params->abi_version > QT_ABI_VERSION) {
|
||||
qt_set_error("qt_init: params->abi_version %d > QT_ABI_VERSION %d (binding compiled against a newer header)",
|
||||
params->abi_version, QT_ABI_VERSION);
|
||||
qt_log(QT_LOG_ERROR, "[Qwen] qt_init params struct is from a newer ABI (%d > %d)", params->abi_version,
|
||||
QT_ABI_VERSION);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
qt_log(QT_LOG_INFO, "[Qwen] qwentts.cpp %s", qwen_version());
|
||||
qt_log(QT_LOG_INFO, "[Qwen] qwentts.cpp %s", qt_version());
|
||||
|
||||
// new qwen_context() value-initialises every field: POD aggregates
|
||||
// new qt_context() value-initialises every field: POD aggregates
|
||||
// (BackendPair, PipelineTTS) are zero-init, std containers in
|
||||
// BPETokenizer construct empty.
|
||||
qwen_context * q = new qwen_context();
|
||||
qt_context * q = new qt_context();
|
||||
|
||||
// The load chain runs inside a try block. Any failure deep in the
|
||||
// GGUF reader, the codec load or the LM weight load throws via
|
||||
// qt_throw; the catch funnels every variant into one cleanup via
|
||||
// qwen_free, which is idempotent on partial state (NULL-safe sched,
|
||||
// qt_free, which is idempotent on partial state (NULL-safe sched,
|
||||
// NULL GGUF handles, refcount-correct backend release).
|
||||
try {
|
||||
q->bp = backend_init("Talker");
|
||||
if (!q->bp.backend) {
|
||||
qt_throw("qwen_init: backend_init failed (no GGML backend available)");
|
||||
qt_throw("qt_init: backend_init failed (no GGML backend available)");
|
||||
}
|
||||
|
||||
if (!pipeline_tts_load(&q->pt, params->talker_path, params->codec_path, q->bp)) {
|
||||
qt_throw("qwen_init: pipeline_tts_load failed for '%s' / '%s'", params->talker_path, params->codec_path);
|
||||
qt_throw("qt_init: pipeline_tts_load failed for '%s' / '%s'", params->talker_path, params->codec_path);
|
||||
}
|
||||
|
||||
// BPE tokenizer payload lives inside the talker GGUF. Load the
|
||||
@@ -246,7 +240,7 @@ struct qwen_context * qwen_init(const struct qwen_init_params * params) {
|
||||
// specials key list mirrors what the standalone CLI used to
|
||||
// do before the facade hoisted the load chain.
|
||||
if (!load_bpe_from_gguf(&q->tok, params->talker_path)) {
|
||||
qt_throw("qwen_init: load_bpe_from_gguf failed for '%s'", params->talker_path);
|
||||
qt_throw("qt_init: load_bpe_from_gguf failed for '%s'", params->talker_path);
|
||||
}
|
||||
const char * specials_keys[] = {
|
||||
"qwen3-tts.text.im_start_id", "qwen3-tts.text.im_end_id", "qwen3-tts.text.tts_pad_id",
|
||||
@@ -256,14 +250,14 @@ struct qwen_context * qwen_init(const struct qwen_init_params * params) {
|
||||
} catch (const std::exception & e) {
|
||||
qt_set_error("%s", e.what());
|
||||
qt_log(QT_LOG_ERROR, "[Qwen] %s", e.what());
|
||||
qwen_free(q);
|
||||
qt_free(q);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return q;
|
||||
}
|
||||
|
||||
void qwen_free(struct qwen_context * q) {
|
||||
void qt_free(struct qt_context * q) {
|
||||
if (!q) {
|
||||
return;
|
||||
}
|
||||
@@ -274,7 +268,7 @@ void qwen_free(struct qwen_context * q) {
|
||||
|
||||
// Resolve a -1 seed to a hardware random 64-bit value. Anything else is
|
||||
// forwarded verbatim, so reproducibility is one explicit seed away.
|
||||
static int64_t qwen_resolve_seed(int64_t seed) {
|
||||
static int64_t qt_resolve_seed(int64_t seed) {
|
||||
if (seed >= 0) {
|
||||
return seed;
|
||||
}
|
||||
@@ -282,22 +276,20 @@ static int64_t qwen_resolve_seed(int64_t seed) {
|
||||
return (int64_t) (((uint64_t) rd() << 32) ^ (uint64_t) rd());
|
||||
}
|
||||
|
||||
enum qwen_status qwen_synthesize(struct qwen_context * q,
|
||||
const struct qwen_tts_params * params,
|
||||
struct qwen_audio * out) {
|
||||
enum qt_status qt_synthesize(struct qt_context * q, const struct qt_tts_params * params, struct qt_audio * out) {
|
||||
if (!q || !params || !out) {
|
||||
qt_set_error("qwen_synthesize: q, params or out is NULL");
|
||||
qt_set_error("qt_synthesize: q, params or out is NULL");
|
||||
if (out) {
|
||||
qwen_audio_free(out);
|
||||
qt_audio_free(out);
|
||||
}
|
||||
return QWEN_STATUS_INVALID_PARAMS;
|
||||
return QT_STATUS_INVALID_PARAMS;
|
||||
}
|
||||
if (params->abi_version > QWEN_ABI_VERSION) {
|
||||
if (params->abi_version > QT_ABI_VERSION) {
|
||||
qt_set_error(
|
||||
"qwen_synthesize: params->abi_version %d > QWEN_ABI_VERSION %d (binding compiled against a newer header)",
|
||||
params->abi_version, QWEN_ABI_VERSION);
|
||||
qwen_audio_free(out);
|
||||
return QWEN_STATUS_INVALID_PARAMS;
|
||||
"qt_synthesize: params->abi_version %d > QT_ABI_VERSION %d (binding compiled against a newer header)",
|
||||
params->abi_version, QT_ABI_VERSION);
|
||||
qt_audio_free(out);
|
||||
return QT_STATUS_INVALID_PARAMS;
|
||||
}
|
||||
|
||||
// Mode validation. Mirrors the upstream Python which raises
|
||||
@@ -309,38 +301,38 @@ enum qwen_status qwen_synthesize(struct qwen_context * q,
|
||||
const std::string & mt = q->pt.model_type;
|
||||
if (params->speaker && mt != "custom_voice") {
|
||||
qt_set_error("--speaker is only valid for custom_voice models (loaded: %s)", mt.c_str());
|
||||
qwen_audio_free(out);
|
||||
return QWEN_STATUS_MODE_INVALID;
|
||||
qt_audio_free(out);
|
||||
return QT_STATUS_MODE_INVALID;
|
||||
}
|
||||
if (params->instruct && mt == "base") {
|
||||
qt_set_error("--instruct is not supported for base models");
|
||||
qwen_audio_free(out);
|
||||
return QWEN_STATUS_MODE_INVALID;
|
||||
qt_audio_free(out);
|
||||
return QT_STATUS_MODE_INVALID;
|
||||
}
|
||||
if (mt == "custom_voice" && !params->speaker) {
|
||||
qt_set_error("custom_voice models require --speaker");
|
||||
qwen_audio_free(out);
|
||||
return QWEN_STATUS_MODE_INVALID;
|
||||
qt_audio_free(out);
|
||||
return QT_STATUS_MODE_INVALID;
|
||||
}
|
||||
if (mt == "voice_design" && (!params->instruct || params->instruct[0] == '\0')) {
|
||||
qt_set_error("voice_design models require --instruct");
|
||||
qwen_audio_free(out);
|
||||
return QWEN_STATUS_MODE_INVALID;
|
||||
qt_audio_free(out);
|
||||
return QT_STATUS_MODE_INVALID;
|
||||
}
|
||||
if (params->ref_audio_24k && mt != "base") {
|
||||
qt_set_error("--ref-wav is only valid for base models (loaded: %s)", mt.c_str());
|
||||
qwen_audio_free(out);
|
||||
return QWEN_STATUS_MODE_INVALID;
|
||||
qt_audio_free(out);
|
||||
return QT_STATUS_MODE_INVALID;
|
||||
}
|
||||
if (params->speaker && params->ref_audio_24k) {
|
||||
qt_set_error("--speaker and --ref-wav are mutually exclusive");
|
||||
qwen_audio_free(out);
|
||||
return QWEN_STATUS_INVALID_PARAMS;
|
||||
qt_audio_free(out);
|
||||
return QT_STATUS_INVALID_PARAMS;
|
||||
}
|
||||
if (params->ref_text && !params->ref_audio_24k) {
|
||||
qt_set_error("--ref-text requires --ref-wav");
|
||||
qwen_audio_free(out);
|
||||
return QWEN_STATUS_INVALID_PARAMS;
|
||||
qt_audio_free(out);
|
||||
return QT_STATUS_INVALID_PARAMS;
|
||||
}
|
||||
|
||||
// Translate the public POD params into the internal C++ struct
|
||||
@@ -356,7 +348,7 @@ enum qwen_status qwen_synthesize(struct qwen_context * q,
|
||||
p.ref_audio_24k = params->ref_audio_24k;
|
||||
p.ref_n_samples = params->ref_n_samples;
|
||||
p.ref_text = params->ref_text;
|
||||
p.seed = qwen_resolve_seed(params->seed);
|
||||
p.seed = qt_resolve_seed(params->seed);
|
||||
p.max_new_tokens = params->max_new_tokens;
|
||||
p.do_sample = params->do_sample;
|
||||
p.temperature = params->temperature;
|
||||
@@ -372,26 +364,26 @@ enum qwen_status qwen_synthesize(struct qwen_context * q,
|
||||
// Defense in depth: the synthesis path normally reports failures
|
||||
// via bool return + qt_set_error. A future load-style throw or any
|
||||
// std::bad_alloc deep inside the GGML backend is caught here and
|
||||
// converted to QWEN_STATUS_GENERATE_FAILED so an exception never
|
||||
// converted to QT_STATUS_GENERATE_FAILED so an exception never
|
||||
// crosses the extern "C" boundary.
|
||||
try {
|
||||
PipelineTTSSynthesizeOutput pout;
|
||||
if (!pipeline_tts_synthesize(&q->pt, &q->tok, p, &pout)) {
|
||||
qwen_audio_free(out);
|
||||
return QWEN_STATUS_GENERATE_FAILED;
|
||||
qt_audio_free(out);
|
||||
return QT_STATUS_GENERATE_FAILED;
|
||||
}
|
||||
|
||||
// Copy the std::vector<float> into a malloc-backed buffer the
|
||||
// caller can free with std::free via qwen_audio_free. The
|
||||
// caller can free with std::free via qt_audio_free. The
|
||||
// vector itself goes out of scope at function exit, releasing
|
||||
// its own storage independently.
|
||||
const size_t n = pout.audio.size();
|
||||
const size_t bytes = n * sizeof(float);
|
||||
float * buf = (float *) std::malloc(bytes > 0 ? bytes : 1);
|
||||
if (!buf) {
|
||||
qt_set_error("qwen_synthesize: malloc failed for %zu samples", n);
|
||||
qwen_audio_free(out);
|
||||
return QWEN_STATUS_OOM;
|
||||
qt_set_error("qt_synthesize: malloc failed for %zu samples", n);
|
||||
qt_audio_free(out);
|
||||
return QT_STATUS_OOM;
|
||||
}
|
||||
if (n > 0) {
|
||||
std::memcpy(buf, pout.audio.data(), bytes);
|
||||
@@ -400,12 +392,12 @@ enum qwen_status qwen_synthesize(struct qwen_context * q,
|
||||
out->n_samples = (int) n;
|
||||
out->sample_rate = pout.sample_rate;
|
||||
out->channels = 1;
|
||||
return QWEN_STATUS_OK;
|
||||
return QT_STATUS_OK;
|
||||
} catch (const std::exception & e) {
|
||||
qt_set_error("%s", e.what());
|
||||
qt_log(QT_LOG_ERROR, "[Qwen] %s", e.what());
|
||||
qwen_audio_free(out);
|
||||
return QWEN_STATUS_GENERATE_FAILED;
|
||||
qt_audio_free(out);
|
||||
return QT_STATUS_GENERATE_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+60
-62
@@ -4,10 +4,10 @@
|
||||
// Single-header public API. Pure C99, consumable from C and C++ alike.
|
||||
// Bindings (Python ctypes, Rust bindgen, Go cgo) parse this file directly.
|
||||
// Style follows whisper.h / llama.h / omnivoice.h: extern "C" linkage on
|
||||
// every entry, POD structs only, const char * UTF-8 strings, qwen_status
|
||||
// every entry, POD structs only, const char * UTF-8 strings, qt_status
|
||||
// enum returns.
|
||||
//
|
||||
// The opaque qwen_context handle aggregates every module the synthesis
|
||||
// The opaque qt_context handle aggregates every module the synthesis
|
||||
// path needs (Talker LM weights, code predictor MTP head, optional
|
||||
// speaker encoder, 12 Hz audio tokenizer codec, BPE tokenizer, GGML
|
||||
// backend pair). One init, one free, one synthesize call covers the
|
||||
@@ -24,28 +24,28 @@ extern "C" {
|
||||
#endif
|
||||
|
||||
// Symbol visibility. Three Windows cases: building the SHARED target
|
||||
// (QWENTTS_BUILD set, dllexport), consuming the SHARED target from
|
||||
// (QWEN_BUILD set, dllexport), consuming the SHARED target from
|
||||
// outside (nothing set, dllimport), consuming the STATIC archive
|
||||
// (QWENTTS_STATIC set by the static target's INTERFACE definitions,
|
||||
// (QWEN_STATIC set by the static target's INTERFACE definitions,
|
||||
// empty so the linker resolves the symbol directly without dllimport).
|
||||
// On GCC/Clang the default-visibility attribute is harmless on static
|
||||
// builds and required on shared builds.
|
||||
#if defined(_WIN32) || defined(__CYGWIN__)
|
||||
# if defined(QWENTTS_STATIC)
|
||||
# define QWEN_API
|
||||
# elif defined(QWENTTS_BUILD)
|
||||
# define QWEN_API __declspec(dllexport)
|
||||
# if defined(QWEN_STATIC)
|
||||
# define QT_API
|
||||
# elif defined(QWEN_BUILD)
|
||||
# define QT_API __declspec(dllexport)
|
||||
# else
|
||||
# define QWEN_API __declspec(dllimport)
|
||||
# define QT_API __declspec(dllimport)
|
||||
# endif
|
||||
#elif defined(__GNUC__) || defined(__clang__)
|
||||
# define QWEN_API __attribute__((visibility("default")))
|
||||
# define QT_API __attribute__((visibility("default")))
|
||||
#else
|
||||
# define QWEN_API
|
||||
# define QT_API
|
||||
#endif
|
||||
|
||||
// Struct ABI version. Incremented every time a public POD struct grows a
|
||||
// new field at the end. Callers fill `.abi_version = QWEN_ABI_VERSION`
|
||||
// new field at the end. Callers fill `.abi_version = QT_ABI_VERSION`
|
||||
// (or let qwen_*_default_params set it). Entries that consume those
|
||||
// structs reject inputs whose abi_version exceeds the build-time
|
||||
// constant: this guards a binary built against vN from receiving a
|
||||
@@ -54,43 +54,43 @@ extern "C" {
|
||||
// callers and the lib reads only what its abi_version permits.
|
||||
//
|
||||
// There is no separate semver triple. The runtime build identity is the
|
||||
// git short hash + commit date string returned by qwen_version(); for
|
||||
// binding compat checks, QWEN_ABI_VERSION is the only number that
|
||||
// git short hash + commit date string returned by qt_version(); for
|
||||
// binding compat checks, QT_ABI_VERSION is the only number that
|
||||
// matters. Aligned on OV_ABI_VERSION = 2 for the omnivoice ABI cousin.
|
||||
#define QWEN_ABI_VERSION 2
|
||||
#define QT_ABI_VERSION 2
|
||||
|
||||
// Returns a static string of the form "<git-hash> (<date>)" identifying
|
||||
// the exact commit this binary was built from. Safe to call from any
|
||||
// thread, no allocation. Pointer stays valid for the process lifetime.
|
||||
QWEN_API const char * qwen_version(void);
|
||||
QT_API const char * qt_version(void);
|
||||
|
||||
// Status code returned by every fallible entry. QWEN_STATUS_OK is always
|
||||
// zero so `if (rc)` reads as `if (rc != QWEN_STATUS_OK)`.
|
||||
enum qwen_status {
|
||||
QWEN_STATUS_OK = 0,
|
||||
QWEN_STATUS_INVALID_PARAMS = -1,
|
||||
QWEN_STATUS_MODE_INVALID = -2,
|
||||
QWEN_STATUS_GENERATE_FAILED = -3,
|
||||
QWEN_STATUS_OOM = -4,
|
||||
// Status code returned by every fallible entry. QT_STATUS_OK is always
|
||||
// zero so `if (rc)` reads as `if (rc != QT_STATUS_OK)`.
|
||||
enum qt_status {
|
||||
QT_STATUS_OK = 0,
|
||||
QT_STATUS_INVALID_PARAMS = -1,
|
||||
QT_STATUS_MODE_INVALID = -2,
|
||||
QT_STATUS_GENERATE_FAILED = -3,
|
||||
QT_STATUS_OOM = -4,
|
||||
};
|
||||
|
||||
// Returns the last error message produced on the calling thread by any
|
||||
// qwen_* entry, as a NUL terminated UTF-8 string. errno-style semantics:
|
||||
// the pointer is only meaningful right after a failure (qwen_init
|
||||
// returning NULL, or any qwen_* entry returning a negative qwen_status);
|
||||
// the pointer is only meaningful right after a failure (qt_init
|
||||
// returning NULL, or any qwen_* entry returning a negative qt_status);
|
||||
// calling it after a successful entry yields the previous message or an
|
||||
// empty string. Storage is thread local so two threads running
|
||||
// qwen_synthesize concurrently never race on each other's diagnostics.
|
||||
// qt_synthesize concurrently never race on each other's diagnostics.
|
||||
// The pointer stays valid until the next failing qwen_* entry on the
|
||||
// same thread.
|
||||
QWEN_API const char * qwen_last_error(void);
|
||||
QT_API const char * qt_last_error(void);
|
||||
|
||||
// Output audio buffer. Plain POD: the samples pointer is malloc
|
||||
// allocated by qwen_synthesize, owned by the struct, released by
|
||||
// qwen_audio_free. Do not free samples directly nor reassign without
|
||||
// allocated by qt_synthesize, owned by the struct, released by
|
||||
// qt_audio_free. Do not free samples directly nor reassign without
|
||||
// freeing first. Zero initialise before the first use:
|
||||
// `struct qwen_audio a = {0};`.
|
||||
struct qwen_audio {
|
||||
// `struct qt_audio a = {0};`.
|
||||
struct qt_audio {
|
||||
float * samples; // mono PCM, malloc allocated
|
||||
int n_samples; // length in samples
|
||||
int sample_rate; // 24000 for the 12 Hz Qwen3-TTS tokenizer
|
||||
@@ -99,10 +99,10 @@ struct qwen_audio {
|
||||
|
||||
// Release the samples buffer and reset the struct to empty. Safe on a
|
||||
// zero initialised struct (no double free, no NULL deref).
|
||||
QWEN_API void qwen_audio_free(struct qwen_audio * a);
|
||||
QT_API void qt_audio_free(struct qt_audio * a);
|
||||
|
||||
// Opaque handle. Definition lives in qwen.cpp. Use qwen_init / qwen_free.
|
||||
struct qwen_context;
|
||||
// Opaque handle. Definition lives in qwen.cpp. Use qt_init / qt_free.
|
||||
struct qt_context;
|
||||
|
||||
// Initialisation parameters. Both GGUF paths are required: the talker
|
||||
// GGUF holds the LM weights, the code predictor MTP head and (for
|
||||
@@ -111,63 +111,63 @@ struct qwen_context;
|
||||
// so a future struct growth keeps reading the version field at offset
|
||||
// 0. No use_fa / clamp_fp16 yet: the current pipeline_tts_load picks
|
||||
// flash attention from backend capability without a user knob.
|
||||
struct qwen_init_params {
|
||||
struct qt_init_params {
|
||||
int abi_version;
|
||||
const char * talker_path;
|
||||
const char * codec_path;
|
||||
};
|
||||
|
||||
// Initialise to the standard defaults: both paths NULL (caller must set
|
||||
// them before calling qwen_init), abi_version set to QWEN_ABI_VERSION.
|
||||
QWEN_API void qwen_init_default_params(struct qwen_init_params * p);
|
||||
// them before calling qt_init), abi_version set to QT_ABI_VERSION.
|
||||
QT_API void qt_init_default_params(struct qt_init_params * p);
|
||||
|
||||
// Allocate every module described by params. Returns NULL on any
|
||||
// failure after releasing whatever it has allocated so far. The
|
||||
// returned handle owns its GGML backend pair and must be released with
|
||||
// qwen_free.
|
||||
QWEN_API struct qwen_context * qwen_init(const struct qwen_init_params * params);
|
||||
// qt_free.
|
||||
QT_API struct qt_context * qt_init(const struct qt_init_params * params);
|
||||
|
||||
// Release every module owned by the handle and free the handle itself.
|
||||
// Safe on NULL.
|
||||
QWEN_API void qwen_free(struct qwen_context * q);
|
||||
QT_API void qt_free(struct qt_context * q);
|
||||
|
||||
// Log severity. Numerically ordered so a callback can filter with a
|
||||
// simple `if (level < threshold) return;`. ERROR is reserved for
|
||||
// failure reports that the lib also surfaces via qwen_status /
|
||||
// qwen_last_error; WARN for recoverable surprises; INFO for the
|
||||
// failure reports that the lib also surfaces via qt_status /
|
||||
// qt_last_error; WARN for recoverable surprises; INFO for the
|
||||
// normal load and synthesis cadence; DEBUG for tensor-level cossim
|
||||
// diagnostics.
|
||||
enum qwen_log_level {
|
||||
QWEN_LOG_DEBUG = 0,
|
||||
QWEN_LOG_INFO = 1,
|
||||
QWEN_LOG_WARN = 2,
|
||||
QWEN_LOG_ERROR = 3,
|
||||
enum qt_log_level {
|
||||
QT_LOG_DEBUG = 0,
|
||||
QT_LOG_INFO = 1,
|
||||
QT_LOG_WARN = 2,
|
||||
QT_LOG_ERROR = 3,
|
||||
};
|
||||
|
||||
// Logging callback. msg is a NUL terminated UTF-8 string already
|
||||
// formatted by the lib, with no trailing newline (the callback is free
|
||||
// to add one). user_data is forwarded verbatim from qwen_log_set.
|
||||
// to add one). user_data is forwarded verbatim from qt_log_set.
|
||||
// Called from any thread the lib runs on: the callback must be
|
||||
// reentrant.
|
||||
typedef void (*qwen_log_cb)(enum qwen_log_level level, const char * msg, void * user_data);
|
||||
typedef void (*qt_log_cb)(enum qt_log_level level, const char * msg, void * user_data);
|
||||
|
||||
// Install a global log callback. Passing cb == NULL restores the
|
||||
// default behaviour (write to stderr). Safe to call at any point;
|
||||
// takes effect immediately on subsequent log emissions across every
|
||||
// thread. Storage is process wide, not per handle, matching
|
||||
// whisper_log_set / llama_log_set / ov_log_set.
|
||||
QWEN_API void qwen_log_set(qwen_log_cb cb, void * user_data);
|
||||
QT_API void qt_log_set(qt_log_cb cb, void * user_data);
|
||||
|
||||
// Synthesis parameters. Strings are NULL terminated UTF-8; NULL maps
|
||||
// to empty where the underlying pipeline accepts it. The selection
|
||||
// between base / custom_voice / voice_design synthesis mode is driven
|
||||
// by the model_type read from the talker GGUF at qwen_init time, not
|
||||
// by the model_type read from the talker GGUF at qt_init time, not
|
||||
// by an explicit flag here; the seven mode rules are enforced inside
|
||||
// qwen_synthesize and surface as QWEN_STATUS_MODE_INVALID with a
|
||||
// descriptive qwen_last_error(). abi_version stays first so the lib
|
||||
// qt_synthesize and surface as QT_STATUS_MODE_INVALID with a
|
||||
// descriptive qt_last_error(). abi_version stays first so the lib
|
||||
// can route on it before reading any field that may have shifted in a
|
||||
// future minor.
|
||||
struct qwen_tts_params {
|
||||
struct qt_tts_params {
|
||||
int abi_version;
|
||||
|
||||
// Input text and language hint. lang accepts the upstream
|
||||
@@ -190,7 +190,7 @@ struct qwen_tts_params {
|
||||
int ref_n_samples;
|
||||
const char * ref_text;
|
||||
|
||||
// Sampling configuration. seed == -1 is resolved by qwen_synthesize
|
||||
// Sampling configuration. seed == -1 is resolved by qt_synthesize
|
||||
// to a hardware random seed via std::random_device, anything else
|
||||
// is forwarded verbatim for deterministic replay across runs.
|
||||
// Defaults match the upstream Python reference: do_sample true,
|
||||
@@ -217,17 +217,15 @@ struct qwen_tts_params {
|
||||
// max_new_tokens 2048, do_sample true, temperature 0.9, top_k 50,
|
||||
// top_p 1.0, repetition_penalty 1.05, subtalker mirrors talker,
|
||||
// dump_dir NULL.
|
||||
QWEN_API void qwen_tts_default_params(struct qwen_tts_params * p);
|
||||
QT_API void qt_tts_default_params(struct qt_tts_params * p);
|
||||
|
||||
// Run the full TTS synthesis. Validates the params against the loaded
|
||||
// model_type (the seven base / custom_voice / voice_design rules),
|
||||
// resolves the seed, hands off to pipeline_tts_synthesize and fills
|
||||
// `out` with mono float PCM at the codec sample rate. Returns
|
||||
// QWEN_STATUS_OK on success; on any failure returns a negative
|
||||
// qwen_status describing the cause and leaves `out` empty.
|
||||
QWEN_API enum qwen_status qwen_synthesize(struct qwen_context * q,
|
||||
const struct qwen_tts_params * params,
|
||||
struct qwen_audio * out);
|
||||
// QT_STATUS_OK on success; on any failure returns a negative
|
||||
// qt_status describing the cause and leaves `out` empty.
|
||||
QT_API enum qt_status qt_synthesize(struct qt_context * q, const struct qt_tts_params * params, struct qt_audio * out);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
|
||||
+27
-27
@@ -30,7 +30,7 @@
|
||||
#include <cstdlib>
|
||||
#include <string>
|
||||
|
||||
#define QWEN_SEANET_NUM_STAGES 4
|
||||
#define SEANET_NUM_STAGES 4
|
||||
|
||||
struct QwenSEANetResNet {
|
||||
// First conv inside the residual: depthwise reduction by config.compress
|
||||
@@ -58,7 +58,7 @@ struct QwenSEANetEncoder {
|
||||
struct ggml_tensor * init_w;
|
||||
struct ggml_tensor * init_b;
|
||||
|
||||
QwenSEANetStage stages[QWEN_SEANET_NUM_STAGES];
|
||||
QwenSEANetStage stages[SEANET_NUM_STAGES];
|
||||
|
||||
// Last conv: k=last_kernel_size (3), final_dim -> hidden_size (512)
|
||||
struct ggml_tensor * last_w;
|
||||
@@ -76,7 +76,7 @@ struct QwenSEANetEncoder {
|
||||
};
|
||||
|
||||
// Read encoder hyperparameters and bind every SEANet tensor on the backend.
|
||||
static bool qwen_seanet_encoder_load(QwenSEANetEncoder * s, const GGUFModel & gf, ggml_backend_t backend) {
|
||||
static bool seanet_encoder_load(QwenSEANetEncoder * s, const GGUFModel & gf, ggml_backend_t backend) {
|
||||
s->kernel_size = (int) gf_get_u32(gf, "qwen3-tts-tokenizer.encoder.kernel_size");
|
||||
s->residual_kernel_size = (int) gf_get_u32(gf, "qwen3-tts-tokenizer.encoder.residual_kernel_size");
|
||||
s->last_kernel_size = (int) gf_get_u32(gf, "qwen3-tts-tokenizer.encoder.last_kernel_size");
|
||||
@@ -88,22 +88,22 @@ static bool qwen_seanet_encoder_load(QwenSEANetEncoder * s, const GGUFModel & gf
|
||||
// Python downsampling iterates `reversed(upsampling_ratios)` = [4, 5, 6, 8],
|
||||
// so stage 0 applies ratio 4, stage 1 ratio 5, stage 2 ratio 6, stage 3
|
||||
// ratio 8. Cumulative downsample is 4*5*6*8 = 960.
|
||||
int ratios[QWEN_SEANET_NUM_STAGES];
|
||||
int ratios[SEANET_NUM_STAGES];
|
||||
{
|
||||
const auto & arr = gf_get_array_u32(gf, "qwen3-tts-tokenizer.encoder.upsampling_ratios");
|
||||
if ((int) arr.size() != QWEN_SEANET_NUM_STAGES) {
|
||||
if ((int) arr.size() != SEANET_NUM_STAGES) {
|
||||
fprintf(stderr, "[SEANet] FATAL: upsampling_ratios has %d entries, expected %d\n", (int) arr.size(),
|
||||
QWEN_SEANET_NUM_STAGES);
|
||||
SEANET_NUM_STAGES);
|
||||
return false;
|
||||
}
|
||||
for (int i = 0; i < QWEN_SEANET_NUM_STAGES; i++) {
|
||||
ratios[i] = (int) arr[QWEN_SEANET_NUM_STAGES - 1 - i];
|
||||
for (int i = 0; i < SEANET_NUM_STAGES; i++) {
|
||||
ratios[i] = (int) arr[SEANET_NUM_STAGES - 1 - i];
|
||||
}
|
||||
}
|
||||
|
||||
int n_tensors = 4 // init wb + last wb
|
||||
+ QWEN_SEANET_NUM_STAGES * 6 // 4 resnet wb + 2 down wb per stage
|
||||
+ 4; // headroom
|
||||
int n_tensors = 4 // init wb + last wb
|
||||
+ SEANET_NUM_STAGES * 6 // 4 resnet wb + 2 down wb per stage
|
||||
+ 4; // headroom
|
||||
WeightCtx wctx;
|
||||
wctx_init(&wctx, n_tensors);
|
||||
|
||||
@@ -117,7 +117,7 @@ static bool qwen_seanet_encoder_load(QwenSEANetEncoder * s, const GGUFModel & gf
|
||||
static const int DOWN_PY_IDX[] = { 3, 6, 9, 12 };
|
||||
|
||||
int dim = s->num_filters;
|
||||
for (int i = 0; i < QWEN_SEANET_NUM_STAGES; i++) {
|
||||
for (int i = 0; i < SEANET_NUM_STAGES; i++) {
|
||||
QwenSEANetStage & stg = s->stages[i];
|
||||
stg.ratio = ratios[i];
|
||||
stg.in_ch = dim;
|
||||
@@ -159,7 +159,7 @@ static bool qwen_seanet_encoder_load(QwenSEANetEncoder * s, const GGUFModel & gf
|
||||
return true;
|
||||
}
|
||||
|
||||
static void qwen_seanet_encoder_free(QwenSEANetEncoder * s) {
|
||||
static void seanet_encoder_free(QwenSEANetEncoder * s) {
|
||||
if (s->weight_buf) {
|
||||
ggml_backend_buffer_free(s->weight_buf);
|
||||
s->weight_buf = NULL;
|
||||
@@ -172,10 +172,10 @@ static void qwen_seanet_encoder_free(QwenSEANetEncoder * s) {
|
||||
|
||||
// SEANet ResNet forward: skip; ELU; conv k=3,s=1,d=1, dim->dim/2; ELU;
|
||||
// conv k=1, dim/2->dim; add(skip).
|
||||
static struct ggml_tensor * qwen_seanet_resnet_forward(struct ggml_context * ctx,
|
||||
const QwenSEANetResNet * ru,
|
||||
struct ggml_tensor * x,
|
||||
int residual_kernel_size) {
|
||||
static struct ggml_tensor * seanet_resnet_forward(struct ggml_context * ctx,
|
||||
const QwenSEANetResNet * ru,
|
||||
struct ggml_tensor * x,
|
||||
int residual_kernel_size) {
|
||||
struct ggml_tensor * skip = x;
|
||||
x = ggml_elu(ctx, x);
|
||||
x = qwen_causal_conv1d(ctx, ru->c0_w, ru->c0_b, x, residual_kernel_size, 1, 1);
|
||||
@@ -195,22 +195,22 @@ static struct ggml_tensor * qwen_seanet_resnet_forward(struct ggml_context *
|
||||
// stage1_out : post stage 1 (resnet + ELU + downsample 5x), [T_audio/20, 256]
|
||||
// stage3_out : post stage 3 (resnet + ELU + downsample 8x), [T_audio/960, 1024]
|
||||
// Returns [T_audio / 960, 512] f32 T-first.
|
||||
static struct ggml_tensor * qwen_seanet_encoder_forward(struct ggml_context * ctx,
|
||||
const QwenSEANetEncoder * s,
|
||||
struct ggml_tensor * x,
|
||||
struct ggml_tensor ** init_out = NULL,
|
||||
struct ggml_tensor ** resnet0_out = NULL,
|
||||
struct ggml_tensor ** stage0_out = NULL,
|
||||
struct ggml_tensor ** stage1_out = NULL,
|
||||
struct ggml_tensor ** stage3_out = NULL) {
|
||||
static struct ggml_tensor * seanet_encoder_forward(struct ggml_context * ctx,
|
||||
const QwenSEANetEncoder * s,
|
||||
struct ggml_tensor * x,
|
||||
struct ggml_tensor ** init_out = NULL,
|
||||
struct ggml_tensor ** resnet0_out = NULL,
|
||||
struct ggml_tensor ** stage0_out = NULL,
|
||||
struct ggml_tensor ** stage1_out = NULL,
|
||||
struct ggml_tensor ** stage3_out = NULL) {
|
||||
x = qwen_causal_conv1d(ctx, s->init_w, s->init_b, x, s->kernel_size, 1, 1);
|
||||
if (init_out) {
|
||||
*init_out = x;
|
||||
}
|
||||
|
||||
for (int i = 0; i < QWEN_SEANET_NUM_STAGES; i++) {
|
||||
for (int i = 0; i < SEANET_NUM_STAGES; i++) {
|
||||
const QwenSEANetStage & stg = s->stages[i];
|
||||
x = qwen_seanet_resnet_forward(ctx, &stg.resnet, x, s->residual_kernel_size);
|
||||
x = seanet_resnet_forward(ctx, &stg.resnet, x, s->residual_kernel_size);
|
||||
if (i == 0 && resnet0_out) {
|
||||
*resnet0_out = x;
|
||||
}
|
||||
|
||||
+21
-23
@@ -22,7 +22,7 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#define QWEN_TOKENIZER_TRANSFORMER_MAX_LAYERS 16
|
||||
#define TOK_TRANS_MAX_LAYERS 16
|
||||
|
||||
struct QwenTransformerAttention {
|
||||
struct ggml_tensor * q_proj_w; // [hidden, num_q_heads * head_dim]
|
||||
@@ -60,7 +60,7 @@ struct QwenTokenizerTransformer {
|
||||
|
||||
struct ggml_tensor * input_proj_w; // [latent_dim, hidden]
|
||||
struct ggml_tensor * input_proj_b; // [hidden]
|
||||
QwenTransformerLayer layers[QWEN_TOKENIZER_TRANSFORMER_MAX_LAYERS];
|
||||
QwenTransformerLayer layers[TOK_TRANS_MAX_LAYERS];
|
||||
struct ggml_tensor * norm_w; // [hidden]
|
||||
struct ggml_tensor * output_proj_w; // [hidden, latent_dim]
|
||||
struct ggml_tensor * output_proj_b; // [latent_dim]
|
||||
@@ -72,9 +72,7 @@ struct QwenTokenizerTransformer {
|
||||
// Read decoder hyperparameters from GGUF metadata, allocate every weight
|
||||
// tensor on the backend, and bind tensor pointers in the struct. Returns
|
||||
// true on success.
|
||||
static bool qwen_tokenizer_transformer_load(QwenTokenizerTransformer * tr,
|
||||
const GGUFModel & gf,
|
||||
ggml_backend_t backend) {
|
||||
static bool tok_trans_load(QwenTokenizerTransformer * tr, const GGUFModel & gf, ggml_backend_t backend) {
|
||||
tr->hidden_size = (int) gf_get_u32(gf, "qwen3-tts-tokenizer.decoder.hidden_size");
|
||||
tr->latent_dim = (int) gf_get_u32(gf, "qwen3-tts-tokenizer.decoder.latent_dim");
|
||||
tr->num_layers = (int) gf_get_u32(gf, "qwen3-tts-tokenizer.decoder.num_hidden_layers");
|
||||
@@ -86,9 +84,9 @@ static bool qwen_tokenizer_transformer_load(QwenTokenizerTransformer * tr,
|
||||
tr->rope_theta = gf_get_f32(gf, "qwen3-tts-tokenizer.decoder.rope_theta");
|
||||
tr->rms_norm_eps = gf_get_f32(gf, "qwen3-tts-tokenizer.decoder.rms_norm_eps");
|
||||
|
||||
if (tr->num_layers > QWEN_TOKENIZER_TRANSFORMER_MAX_LAYERS) {
|
||||
if (tr->num_layers > TOK_TRANS_MAX_LAYERS) {
|
||||
fprintf(stderr, "[Transformer] FATAL: %d layers exceeds compile-time max %d\n", tr->num_layers,
|
||||
QWEN_TOKENIZER_TRANSFORMER_MAX_LAYERS);
|
||||
TOK_TRANS_MAX_LAYERS);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -152,7 +150,7 @@ static bool qwen_tokenizer_transformer_load(QwenTokenizerTransformer * tr,
|
||||
return true;
|
||||
}
|
||||
|
||||
static void qwen_tokenizer_transformer_free(QwenTokenizerTransformer * tr) {
|
||||
static void tok_trans_free(QwenTokenizerTransformer * tr) {
|
||||
if (tr->weight_buf) {
|
||||
ggml_backend_buffer_free(tr->weight_buf);
|
||||
tr->weight_buf = NULL;
|
||||
@@ -168,7 +166,7 @@ static void qwen_tokenizer_transformer_free(QwenTokenizerTransformer * tr) {
|
||||
// dst[q * T + k] is the additive bias for query q attending to key k.
|
||||
// Causal sliding window: mask[k, q] = 0 if (k <= q AND q - k < window),
|
||||
// else -inf.
|
||||
static void qwen_build_causal_sliding_mask(int T, int sliding_window, std::vector<float> & dst) {
|
||||
static void tok_trans_build_causal_sliding_mask(int T, int sliding_window, std::vector<float> & dst) {
|
||||
dst.assign((size_t) T * (size_t) T, -INFINITY);
|
||||
for (int q = 0; q < T; q++) {
|
||||
int k_min = q - sliding_window + 1;
|
||||
@@ -181,7 +179,7 @@ static void qwen_build_causal_sliding_mask(int T, int sliding_window, std::vecto
|
||||
}
|
||||
}
|
||||
|
||||
static void qwen_build_positions(int T, std::vector<int32_t> & dst) {
|
||||
static void tok_trans_build_positions(int T, std::vector<int32_t> & dst) {
|
||||
dst.resize((size_t) T);
|
||||
for (int i = 0; i < T; i++) {
|
||||
dst[i] = i;
|
||||
@@ -190,13 +188,13 @@ static void qwen_build_positions(int T, std::vector<int32_t> & dst) {
|
||||
|
||||
// One transformer layer: attention block then MLP block, both with
|
||||
// pre-RMSNorm, post-LayerScale and residual connection.
|
||||
static struct ggml_tensor * qwen_transformer_layer_forward(struct ggml_context * ctx,
|
||||
const QwenTokenizerTransformer * tr,
|
||||
const QwenTransformerLayer & layer,
|
||||
struct ggml_tensor * x,
|
||||
struct ggml_tensor * positions,
|
||||
struct ggml_tensor * mask,
|
||||
int T) {
|
||||
static struct ggml_tensor * tok_trans_layer_forward(struct ggml_context * ctx,
|
||||
const QwenTokenizerTransformer * tr,
|
||||
const QwenTransformerLayer & layer,
|
||||
struct ggml_tensor * x,
|
||||
struct ggml_tensor * positions,
|
||||
struct ggml_tensor * mask,
|
||||
int T) {
|
||||
int hidden = tr->hidden_size;
|
||||
int n_q_heads = tr->num_attention_heads;
|
||||
int n_kv = tr->num_kv_heads;
|
||||
@@ -269,11 +267,11 @@ static struct ggml_tensor * qwen_transformer_layer_forward(struct ggml_context *
|
||||
// positions: [T] i32
|
||||
// mask : [T, T] f32, additive (-inf where masked)
|
||||
// returns : [latent_dim, T] f32
|
||||
static struct ggml_tensor * qwen_tokenizer_transformer_forward(struct ggml_context * ctx,
|
||||
const QwenTokenizerTransformer * tr,
|
||||
struct ggml_tensor * x,
|
||||
struct ggml_tensor * positions,
|
||||
struct ggml_tensor * mask) {
|
||||
static struct ggml_tensor * tok_trans_forward(struct ggml_context * ctx,
|
||||
const QwenTokenizerTransformer * tr,
|
||||
struct ggml_tensor * x,
|
||||
struct ggml_tensor * positions,
|
||||
struct ggml_tensor * mask) {
|
||||
int T = (int) x->ne[1];
|
||||
|
||||
// input_proj: [latent_dim, T] -> [hidden, T]
|
||||
@@ -281,7 +279,7 @@ static struct ggml_tensor * qwen_tokenizer_transformer_forward(struct ggml_conte
|
||||
h = ggml_add(ctx, h, tr->input_proj_b);
|
||||
|
||||
for (int l = 0; l < tr->num_layers; l++) {
|
||||
h = qwen_transformer_layer_forward(ctx, tr, tr->layers[l], h, positions, mask, T);
|
||||
h = tok_trans_layer_forward(ctx, tr, tr->layers[l], h, positions, mask, T);
|
||||
}
|
||||
|
||||
h = ggml_rms_norm(ctx, h, tr->rms_norm_eps);
|
||||
|
||||
Reference in New Issue
Block a user