192 lines
7.9 KiB
C++
192 lines
7.9 KiB
C++
#pragma once
|
|
// convnext-block.h: 2-block upsample stage for the Qwen3-TTS 12Hz
|
|
// tokenizer decoder.
|
|
//
|
|
// Each block is a CausalTransConv1d (kernel 2, stride 2) followed by a
|
|
// ConvNeXt block. The ConvNeXt block is :
|
|
// x = x + gamma * pwconv2(gelu(pwconv1(layernorm(dwconv(x)))))
|
|
// where dwconv is a depthwise causal Conv1d (kernel 7, dilation 1),
|
|
// pwconv1 / pwconv2 are pointwise Linears (1024 -> 4096 -> 1024), and
|
|
// gamma is a per-channel LayerScale parameter. The two blocks together
|
|
// upsample the temporal axis by 4x while keeping the channel count at
|
|
// latent_dim (1024).
|
|
|
|
#include "causal-trans-conv.h"
|
|
#include "ggml-backend.h"
|
|
#include "ggml.h"
|
|
#include "gguf-weights.h"
|
|
#include "weight-ctx.h"
|
|
|
|
#include <cstdio>
|
|
#include <cstdlib>
|
|
#include <string>
|
|
|
|
#define UPSAMPLE_MAX_BLOCKS 2
|
|
|
|
struct QwenConvNeXtBlock {
|
|
struct ggml_tensor * dwconv_w; // [K=7, 1, C] depthwise weight
|
|
struct ggml_tensor * dwconv_b; // [C]
|
|
struct ggml_tensor * norm_w; // LayerNorm gain [C]
|
|
struct ggml_tensor * norm_b; // LayerNorm bias [C]
|
|
struct ggml_tensor * pwconv1_w; // [C, 4*C] (in, out)
|
|
struct ggml_tensor * pwconv1_b; // [4*C]
|
|
struct ggml_tensor * pwconv2_w; // [4*C, C]
|
|
struct ggml_tensor * pwconv2_b; // [C]
|
|
struct ggml_tensor * gamma; // LayerScale [C]
|
|
};
|
|
|
|
struct QwenUpsampleStage {
|
|
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[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;
|
|
};
|
|
|
|
// Read upsample hyperparameters from GGUF metadata, allocate every weight
|
|
// tensor on the backend, and bind tensor pointers in the struct.
|
|
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 > UPSAMPLE_MAX_BLOCKS) {
|
|
fprintf(stderr, "[Upsample] FATAL: %d blocks exceeds compile-time max %d\n", stage->num_blocks,
|
|
UPSAMPLE_MAX_BLOCKS);
|
|
return false;
|
|
}
|
|
|
|
int n_tensors = stage->num_blocks * 11 + 4; // 2 transconv + 9 convnext per block, plus headroom
|
|
WeightCtx wctx;
|
|
wctx_init(&wctx, n_tensors);
|
|
|
|
for (int i = 0; i < stage->num_blocks; i++) {
|
|
char name[160];
|
|
|
|
snprintf(name, sizeof(name), "tok_dec.upsample.%d.conv.weight", i);
|
|
stage->transconv_w[i] = qwen_load_ctw_f32(&wctx, gf, name);
|
|
snprintf(name, sizeof(name), "tok_dec.upsample.%d.conv.bias", i);
|
|
stage->transconv_b[i] = gf_load_tensor(&wctx, gf, name);
|
|
|
|
QwenConvNeXtBlock & cn = stage->convnext[i];
|
|
snprintf(name, sizeof(name), "tok_dec.upsample.%d.dwconv.weight", i);
|
|
cn.dwconv_w = gf_load_conv(&wctx, gf, name);
|
|
snprintf(name, sizeof(name), "tok_dec.upsample.%d.dwconv.bias", i);
|
|
cn.dwconv_b = gf_load_tensor(&wctx, gf, name);
|
|
snprintf(name, sizeof(name), "tok_dec.upsample.%d.norm.weight", i);
|
|
cn.norm_w = gf_load_tensor(&wctx, gf, name);
|
|
snprintf(name, sizeof(name), "tok_dec.upsample.%d.norm.bias", i);
|
|
cn.norm_b = gf_load_tensor(&wctx, gf, name);
|
|
snprintf(name, sizeof(name), "tok_dec.upsample.%d.pwconv1.weight", i);
|
|
cn.pwconv1_w = gf_load_tensor(&wctx, gf, name);
|
|
snprintf(name, sizeof(name), "tok_dec.upsample.%d.pwconv1.bias", i);
|
|
cn.pwconv1_b = gf_load_tensor(&wctx, gf, name);
|
|
snprintf(name, sizeof(name), "tok_dec.upsample.%d.pwconv2.weight", i);
|
|
cn.pwconv2_w = gf_load_tensor(&wctx, gf, name);
|
|
snprintf(name, sizeof(name), "tok_dec.upsample.%d.pwconv2.bias", i);
|
|
cn.pwconv2_b = gf_load_tensor(&wctx, gf, name);
|
|
snprintf(name, sizeof(name), "tok_dec.upsample.%d.gamma", i);
|
|
cn.gamma = gf_load_tensor(&wctx, gf, name);
|
|
}
|
|
|
|
if (!wctx_alloc(&wctx, backend)) {
|
|
fprintf(stderr, "[Upsample] FATAL: backend allocation failed\n");
|
|
return false;
|
|
}
|
|
stage->weight_ctx = wctx.ctx;
|
|
stage->weight_buf = wctx.buffer;
|
|
|
|
fprintf(stderr,
|
|
"[Upsample] Loaded: %d blocks (%dx ratio per block), channels %d, "
|
|
"dwconv kernel %d\n",
|
|
stage->num_blocks, stage->upsample_ratio, stage->channels, stage->dwconv_kernel);
|
|
return true;
|
|
}
|
|
|
|
static void upsample_stage_free(QwenUpsampleStage * stage) {
|
|
if (stage->weight_buf) {
|
|
ggml_backend_buffer_free(stage->weight_buf);
|
|
stage->weight_buf = NULL;
|
|
}
|
|
if (stage->weight_ctx) {
|
|
ggml_free(stage->weight_ctx);
|
|
stage->weight_ctx = NULL;
|
|
}
|
|
}
|
|
|
|
// One ConvNeXt block forward.
|
|
// x: [T, C] f32 T-first
|
|
// returns [T, C] f32 T-first
|
|
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];
|
|
|
|
struct ggml_tensor * residual = x;
|
|
|
|
// dwconv: depthwise causal Conv1d. ggml_conv_1d_dw expects [T, C, B=1].
|
|
// Pre-pad left by (kernel-1) zeros for causal behavior, no internal padding.
|
|
struct ggml_tensor * y = ggml_reshape_3d(ctx, x, T, C, 1);
|
|
y = ggml_pad_ext(ctx, y, kernel - 1, 0, 0, 0, 0, 0, 0, 0);
|
|
y = ggml_conv_1d_dw(ctx, block.dwconv_w, y, 1, 0, 1); // [T, C, 1]
|
|
y = ggml_reshape_2d(ctx, y, T, C);
|
|
if (block.dwconv_b) {
|
|
struct ggml_tensor * b2d = ggml_reshape_2d(ctx, block.dwconv_b, 1, C); // (1, C) broadcasts on T
|
|
y = ggml_add(ctx, y, b2d);
|
|
}
|
|
|
|
// LayerNorm wants the channel dim on ne[0]: transpose to [C, T].
|
|
y = ggml_cont(ctx, ggml_transpose(ctx, y));
|
|
y = ggml_norm(ctx, y, 1e-6f);
|
|
y = ggml_mul(ctx, y, block.norm_w);
|
|
y = ggml_add(ctx, y, block.norm_b);
|
|
|
|
// pwconv1: Linear C -> 4*C. mul_mat contracts ne[0]=C of weight against
|
|
// ne[0]=C of input.
|
|
y = ggml_mul_mat(ctx, block.pwconv1_w, y);
|
|
y = ggml_add(ctx, y, block.pwconv1_b);
|
|
|
|
y = ggml_gelu(ctx, y);
|
|
|
|
// pwconv2: Linear 4*C -> C
|
|
y = ggml_mul_mat(ctx, block.pwconv2_w, y);
|
|
y = ggml_add(ctx, y, block.pwconv2_b);
|
|
|
|
// LayerScale gamma broadcast over T axis (ne[1]).
|
|
y = ggml_mul(ctx, y, block.gamma);
|
|
|
|
// Back to T-first [T, C] to match the residual layout.
|
|
y = ggml_cont(ctx, ggml_transpose(ctx, y));
|
|
|
|
y = ggml_add(ctx, y, residual);
|
|
return y;
|
|
}
|
|
|
|
// Full upsample stage forward: 2 (CausalTransConv + ConvNeXt) blocks.
|
|
// x: [T, C] f32 T-first
|
|
// returns [T * 4, C] f32 T-first
|
|
//
|
|
// 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 * 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 = convnext_block_forward(ctx, stage->convnext[i], x, stage->dwconv_kernel);
|
|
}
|
|
return x;
|
|
}
|