initial files for ace step

This commit is contained in:
Concedo 2026-02-22 13:22:24 +08:00
parent ac70ca35dd
commit 5cd6e50eab
17 changed files with 7346 additions and 1 deletions

View file

@ -712,7 +712,7 @@ gpttype_adapter_vulkan_noavx2.o: $(GPTTYPE_ADAPTER)
$(CXX) $(CXXFLAGS) $(FAILSAFE_FLAGS) $(VULKAN_FLAGS) -c $< -o $@
clean:
rm -vf *.o main ttsmain sdmain whispermain quantize_gguf quantize_clip quantize_gpt2 quantize_gptj quantize_neox quantize_mpt vulkan-shaders-gen vulkan-shaders-gen-noext gguf-split mtmd-cli mainvk fitparams embedding embeddingvk embeddingvk.exe embedding.exe fitparams.exe mainvk.exe mtmd-cli.exe gguf-split.exe vulkan-shaders-gen.exe vulkan-shaders-gen-noext.exe main.exe ttsmain.exe sdmain.exe whispermain.exe quantize_clip.exe quantize_gguf.exe quantize_gptj.exe quantize_gpt2.exe quantize_neox.exe quantize_mpt.exe koboldcpp_default.dll koboldcpp_failsafe.dll koboldcpp_noavx2.dll koboldcpp_vulkan_failsafe.dll koboldcpp_cublas.dll koboldcpp_hipblas.dll koboldcpp_vulkan.dll koboldcpp_vulkan_noavx2.dll koboldcpp_default.so koboldcpp_failsafe.so koboldcpp_noavx2.so koboldcpp_vulkan_failsafe.so koboldcpp_cublas.so koboldcpp_hipblas.so koboldcpp_vulkan.so koboldcpp_vulkan_noavx2.so ggml/src/ggml-vulkan-shaders.cpp ggml/src/ggml-vulkan-shaders.hpp ggml/src/ggml-vulkan-shaders-noext.cpp ggml/src/ggml-vulkan-shaders-noext.hpp
rm -vf *.o main ttsmain sdmain whispermain quantize_gguf quantize_clip quantize_gpt2 quantize_gptj quantize_neox quantize_mpt vulkan-shaders-gen vulkan-shaders-gen-noext gguf-split mtmd-cli mainvk fitparams embedding embeddingvk qwen3tts acestep-a acestep-b acestep-b.exe acestep-a.exe qwen3tts.exe embeddingvk.exe embedding.exe fitparams.exe mainvk.exe mtmd-cli.exe gguf-split.exe vulkan-shaders-gen.exe vulkan-shaders-gen-noext.exe main.exe ttsmain.exe sdmain.exe whispermain.exe quantize_clip.exe quantize_gguf.exe quantize_gptj.exe quantize_gpt2.exe quantize_neox.exe quantize_mpt.exe koboldcpp_default.dll koboldcpp_failsafe.dll koboldcpp_noavx2.dll koboldcpp_vulkan_failsafe.dll koboldcpp_cublas.dll koboldcpp_hipblas.dll koboldcpp_vulkan.dll koboldcpp_vulkan_noavx2.dll koboldcpp_default.so koboldcpp_failsafe.so koboldcpp_noavx2.so koboldcpp_vulkan_failsafe.so koboldcpp_cublas.so koboldcpp_hipblas.so koboldcpp_vulkan.so koboldcpp_vulkan_noavx2.so ggml/src/ggml-vulkan-shaders.cpp ggml/src/ggml-vulkan-shaders.hpp ggml/src/ggml-vulkan-shaders-noext.cpp ggml/src/ggml-vulkan-shaders-noext.hpp
rm -vrf ggml/src/ggml-cuda/*.o
rm -vrf ggml/src/ggml-cuda/template-instances/*.o
rm -vrf llguidance
@ -742,6 +742,10 @@ ttscppmain: otherarch/ttscpp/cli/cli.cpp otherarch/ttscpp/cli/playback.cpp other
$(CXX) $(CXXFLAGS) $(filter-out %.h,$^) -o $@ $(LDFLAGS)
qwen3tts: otherarch/qwen3tts/q3ttsmain.cpp otherarch/qwen3tts/qwen3_tts.cpp otherarch/qwen3tts/text_tokenizer.cpp otherarch/qwen3tts/gguf_loader.cpp otherarch/qwen3tts/tts_transformer.cpp otherarch/qwen3tts/audio_tokenizer_decoder.cpp otherarch/qwen3tts/audio_tokenizer_encoder.cpp otherarch/qwen3tts/coreml_code_predictor_stub.cpp ggml.o ggml-cpu.o ggml-ops.o ggml-vec.o ggml-binops.o ggml-unops.o llama.o console.o llavaclip_default.o llava.o ggml-backend_default.o ggml-backend-reg_default.o ggml-repack.o $(OBJS_FULL) $(OBJS)
$(CXX) $(CXXFLAGS) $(filter-out %.h,$^) -o $@ $(LDFLAGS)
acestep-a: otherarch/acestep/ace-qwen3.cpp otherarch/acestep/request.cpp ggml.o ggml-cpu.o ggml-ops.o ggml-vec.o ggml-binops.o ggml-unops.o llama.o console.o llavaclip_default.o llava.o ggml-backend_default.o ggml-backend-reg_default.o ggml-repack.o $(OBJS_FULL) $(OBJS)
$(CXX) $(CXXFLAGS) $(filter-out %.h,$^) -o $@ $(LDFLAGS)
acestep-b: otherarch/acestep/dit-vae.cpp otherarch/acestep/request.cpp ggml.o ggml-cpu.o ggml-ops.o ggml-vec.o ggml-binops.o ggml-unops.o llama.o console.o llavaclip_default.o llava.o ggml-backend_default.o ggml-backend-reg_default.o ggml-repack.o $(OBJS_FULL) $(OBJS)
$(CXX) $(CXXFLAGS) $(filter-out %.h,$^) -o $@ $(LDFLAGS)
ggml/src/ggml-vulkan-shaders.cpp:
ifdef VULKAN_BUILD

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,49 @@
#pragma once
// backend.h: shared GGML backend initialization
//
// All modules use the same pattern: load all backends, pick best GPU,
// keep CPU as fallback. This avoids duplicating init logic across
// qwen3.h, qwen3-lm.h, cond.h, dit.h, vae.h.
#include "ggml-backend.h"
#include "ggml-cpu.h"
#include <cstdio>
#include <cstring>
#include <thread>
struct BackendPair {
ggml_backend_t backend;
ggml_backend_t cpu_backend;
};
// Initialize backends: load all available (CUDA, Metal, Vulkan...),
// pick the best one, keep CPU as fallback.
// label: log prefix, e.g. "DiT", "VAE", "LM"
static BackendPair backend_init(const char * label) {
ggml_backend_load_all();
BackendPair bp = {};
bp.backend = ggml_backend_init_best();
int n_threads = (int)std::thread::hardware_concurrency() / 2;
if (n_threads < 1) n_threads = 1;
// [GGML] If best backend is already CPU, reuse it (avoid 2 CPU instances
// where only one gets the thread count)
bool best_is_cpu = (strcmp(ggml_backend_name(bp.backend), "CPU") == 0);
if (best_is_cpu) {
bp.cpu_backend = bp.backend;
ggml_backend_cpu_set_n_threads(bp.backend, n_threads);
} else {
bp.cpu_backend = ggml_backend_init_by_type(GGML_BACKEND_DEVICE_TYPE_CPU, NULL);
ggml_backend_cpu_set_n_threads(bp.cpu_backend, n_threads);
}
fprintf(stderr, "[Load] %s backend: %s (CPU threads: %d)\n",
label, ggml_backend_name(bp.backend), n_threads);
return bp;
}
// Create a scheduler from a backend pair.
// max_nodes: graph size hint (4096 for small models, 8192 for large)
static ggml_backend_sched_t backend_sched_new(BackendPair bp, int max_nodes) {
ggml_backend_t backends[2] = { bp.backend, bp.cpu_backend };
int n = (bp.backend == bp.cpu_backend) ? 1 : 2;
return ggml_backend_sched_new(backends, NULL, n, max_nodes, false, true);
}

553
otherarch/acestep/bpe.h Normal file
View file

@ -0,0 +1,553 @@
// bpe.h, Qwen3/GPT-2 byte-level BPE tokenizer (CPU-only, no dependencies)
// Loads vocab.json + merges.txt from HuggingFace tokenizer directory.
// Handles: byte-level encoding, GPT-2 regex pre-tokenizer, BPE merges,
// special token recognition (<|endoftext|>).
#pragma once
#include <string>
#include <vector>
#include <unordered_map>
#include <cstdio>
#include <cstring>
#include <cassert>
#include <climits>
#include "gguf.h"
// GPT-2 byte-level encoding table
// Maps byte [0..255] -> Unicode char for BPE vocab keys.
// Printable ASCII stays as-is, control/space bytes get remapped.
static void build_byte_encoder(std::string byte2str[256]) {
// Standard GPT-2 byte encoder
int bs[256], cs[256], n = 0, total = 0;
// Printable ranges that map to themselves
for (int b = '!'; b <= '~'; b++) { bs[total] = b; cs[total] = b; total++; }
for (int b = 0xA1; b <= 0xAC; b++) { bs[total] = b; cs[total] = b; total++; }
for (int b = 0xAE; b <= 0xFF; b++) { bs[total] = b; cs[total] = b; total++; }
// Remaining bytes get mapped to 256+
bool used[256] = {};
for (int i = 0; i < total; i++) used[bs[i]] = true;
for (int b = 0; b < 256; b++) {
if (!used[b]) { bs[total] = b; cs[total] = 256 + n; n++; total++; }
}
assert(total == 256);
// Convert codepoints to UTF-8 strings
for (int i = 0; i < 256; i++) {
int cp = cs[i];
char buf[4];
int len;
if (cp < 0x80) { buf[0] = (char)cp; len = 1; }
else if (cp < 0x800) { buf[0] = (char)(0xC0 | (cp >> 6)); buf[1] = (char)(0x80 | (cp & 0x3F)); len = 2; }
else { buf[0] = (char)(0xE0 | (cp >> 12)); buf[1] = (char)(0x80 | ((cp >> 6) & 0x3F)); buf[2] = (char)(0x80 | (cp & 0x3F)); len = 3; }
byte2str[bs[i]] = std::string(buf, len);
}
}
// UTF-8 helpers
static int utf8_codepoint(const char *s, int *advance) {
unsigned char c = s[0];
if (c < 0x80) { *advance = 1; return c; }
if ((c & 0xE0) == 0xC0) { *advance = 2; return ((c & 0x1F) << 6) | (s[1] & 0x3F); }
if ((c & 0xF0) == 0xE0) { *advance = 3; return ((c & 0x0F) << 12) | ((s[1] & 0x3F) << 6) | (s[2] & 0x3F); }
if ((c & 0xF8) == 0xF0) { *advance = 4; return ((c & 0x07) << 18) | ((s[1] & 0x3F) << 12) | ((s[2] & 0x3F) << 6) | (s[3] & 0x3F); }
*advance = 1; return c; // fallback
}
// Unicode category checks (simplified but covers Latin + common scripts)
static bool is_letter(int cp) {
if ((cp >= 'A' && cp <= 'Z') || (cp >= 'a' && cp <= 'z')) return true;
if (cp < 0x80) return false;
// Latin Extended: U+00C0-U+00D6, U+00D8-U+00F6, U+00F8-U+01BF + Latin Extended-A/B
if (cp >= 0xC0 && cp <= 0x024F && cp != 0xD7 && cp != 0xF7) return true;
// Common CJK, Cyrillic, Greek, Arabic, etc., treat as letters
if (cp >= 0x0370 && cp <= 0x1FFF) return true; // Greek, Cyrillic, Armenian, etc.
if (cp >= 0x2C00 && cp <= 0x2DFF) return true; // Georgian, etc.
if (cp >= 0x3040 && cp <= 0x9FFF) return true; // CJK
if (cp >= 0xAC00 && cp <= 0xD7AF) return true; // Korean
if (cp >= 0xF900 && cp <= 0xFAFF) return true; // CJK compatibility
if (cp >= 0x10000) return true; // SMP, mostly letters/symbols
return false;
}
static bool is_digit(int cp) { return cp >= '0' && cp <= '9'; }
static bool is_whitespace(int cp) {
return cp == ' ' || cp == '\t' || cp == '\n' || cp == '\r' || cp == 0x0B || cp == 0x0C
|| cp == 0xA0 || cp == 0x2000 || cp == 0x2001 || cp == 0x2002 || cp == 0x200B;
}
static bool is_newline(int cp) { return cp == '\n' || cp == '\r'; }
// GPT-2 pre-tokenizer regex (manual implementation)
// Pattern: (?i:'s|'t|'re|'ve|'m|'ll|'d)|[^\r\n\p{L}\p{N}]?\p{L}+|\p{N}|
// \s?[^\s\p{L}\p{N}]+[\r\n]*|\s*[\r\n]+|\s+(?!\S)|\s+
// Splits text into non-overlapping chunks (on original text, not byte-encoded).
static std::vector<std::string> gpt2_pre_tokenize(const std::string &text) {
std::vector<std::string> chunks;
const char *s = text.c_str();
int len = (int)text.size();
int i = 0;
while (i < len) {
int adv;
int cp = utf8_codepoint(s + i, &adv);
// Rule 1: Contractions 's 't 're 've 'm 'll 'd
if ((cp == '\'' || cp == 0x2019) && i + adv < len) {
const char *rest = s + i + adv;
int rlen = len - i - adv;
auto try_match = [&](const char *suffix, int slen) -> bool {
if (rlen >= slen) {
// case-insensitive compare
for (int k = 0; k < slen; k++) {
char c1 = rest[k], c2 = suffix[k];
if (c1 >= 'A' && c1 <= 'Z') c1 += 32;
if (c1 != c2) return false;
}
// next char should NOT be a letter
if (rlen > slen) {
int a2;
int cp2 = utf8_codepoint(rest + slen, &a2);
if (is_letter(cp2)) return false;
}
chunks.push_back(std::string(s + i, adv + slen));
i += adv + slen;
return true;
}
return false;
};
if (try_match("ll", 2)) continue;
if (try_match("re", 2)) continue;
if (try_match("ve", 2)) continue;
if (try_match("s", 1)) continue;
if (try_match("t", 1)) continue;
if (try_match("m", 1)) continue;
if (try_match("d", 1)) continue;
}
// Rule 2: [^\r\n\p{L}\p{N}]?\p{L}+
if (is_letter(cp)) {
int start = i;
i += adv;
while (i < len) {
int a2;
int cp2 = utf8_codepoint(s + i, &a2);
if (!is_letter(cp2)) break;
i += a2;
}
chunks.push_back(std::string(s + start, i - start));
continue;
}
if (!is_newline(cp) && !is_letter(cp) && !is_digit(cp) && !is_whitespace(cp)) {
// Non-letter/number/space, check if followed by letters
int start = i;
int after = i + adv;
if (after < len) {
int a2;
int cp2 = utf8_codepoint(s + after, &a2);
if (is_letter(cp2)) {
i = after + a2;
while (i < len) {
int a3;
int cp3 = utf8_codepoint(s + i, &a3);
if (!is_letter(cp3)) break;
i += a3;
}
chunks.push_back(std::string(s + start, i - start));
continue;
}
}
}
// Rule 3: \p{N}+ (digits, consume consecutively)
if (is_digit(cp)) {
int start = i;
while (i < len && is_digit((unsigned char)s[i])) i++;
// GPT-2 regex matches single \p{N}, let's match one at a time
// to be safe, but in practice consecutive digits usually merge anyway.
// The regex is \p{N} (single digit), so split each digit:
for (int j = start; j < i; j++) {
chunks.push_back(std::string(s + j, 1));
}
continue;
}
// Rule 5: \s*[\r\n]+ (newlines with optional leading whitespace)
if (is_newline(cp)) {
int start = i;
while (i < len && is_newline((unsigned char)s[i])) i++;
chunks.push_back(std::string(s + start, i - start));
continue;
}
// Rule 6: whitespace handling
// Regex order: \s+(?!\S) first (trailing whitespace), then \s+ as fallback
// \s+(?!\S) backtracks: consumes whitespace NOT followed by non-whitespace
// This peels off leading spaces, leaving the last space to combine with the next word
if (is_whitespace(cp)) {
int start = i;
// Find end of whitespace run
int ws_end = i + adv;
while (ws_end < len && is_whitespace((unsigned char)s[ws_end]) && !is_newline((unsigned char)s[ws_end]))
ws_end++;
// Check what follows the whitespace run
bool followed_by_non_ws = (ws_end < len && !is_whitespace((unsigned char)s[ws_end]) && !is_newline((unsigned char)s[ws_end]));
if (followed_by_non_ws && ws_end - start > 1) {
// \s+(?!\S) matches all but the last space
// Leave one space for the next iteration to combine with word
int trailing = ws_end - 1;
chunks.push_back(std::string(s + start, trailing - start));
i = trailing;
continue;
}
// Single space followed by word: combine space + word as one chunk
i = start + adv;
if (i < len) {
int a2;
int cp2 = utf8_codepoint(s + i, &a2);
if (is_letter(cp2)) {
i += a2;
while (i < len) {
int a3;
int cp3 = utf8_codepoint(s + i, &a3);
if (!is_letter(cp3)) break;
i += a3;
}
chunks.push_back(std::string(s + start, i - start));
continue;
}
if (is_digit(cp2)) {
chunks.push_back(std::string(s + start, i - start));
continue;
}
if (!is_whitespace(cp2) && !is_newline(cp2)) {
int pstart = start;
while (i < len) {
int a3;
int cp3 = utf8_codepoint(s + i, &a3);
if (is_whitespace(cp3) || is_letter(cp3) || is_digit(cp3)) break;
i += a3;
}
while (i < len && is_newline((unsigned char)s[i])) i++;
chunks.push_back(std::string(s + pstart, i - pstart));
continue;
}
}
// Trailing whitespace (end of string or before newline), consume all
i = ws_end;
while (i < len) {
int a2;
int cp2 = utf8_codepoint(s + i, &a2);
if (!is_whitespace(cp2)) break;
i += a2;
}
chunks.push_back(std::string(s + start, i - start));
continue;
}
// Rule 4: [^\s\p{L}\p{N}]+[\r\n]* (punctuation/symbols)
{
int start = i;
i += adv;
while (i < len) {
int a2;
int cp2 = utf8_codepoint(s + i, &a2);
if (is_whitespace(cp2) || is_letter(cp2) || is_digit(cp2) || is_newline(cp2)) break;
i += a2;
}
// trailing newlines
while (i < len && is_newline((unsigned char)s[i])) i++;
chunks.push_back(std::string(s + start, i - start));
}
}
return chunks;
}
// BPE tokenizer struct
struct BPETokenizer {
std::unordered_map<std::string, int> vocab; // token_str -> id
std::unordered_map<std::string, int> merges; // "a b" -> rank
std::string byte2str[256]; // byte -> GPT-2 UTF-8 string
int eos_id; // <|endoftext|> = 151643
int n_vocab;
std::vector<std::string> id_to_str; // id -> token_str (reverse vocab)
};
// Minimal JSON parser for vocab.json ({"str": int, ...})
static bool load_vocab_json(const std::string &path, std::unordered_map<std::string, int> &vocab) {
FILE *f = fopen(path.c_str(), "rb");
if (!f) return false;
fseek(f, 0, SEEK_END);
size_t sz = ftell(f);
fseek(f, 0, SEEK_SET);
std::string data(sz, '\0');
fread(&data[0], 1, sz, f);
fclose(f);
size_t i = 0;
auto skip_ws = [&]() { while (i < sz && (data[i] == ' ' || data[i] == '\n' || data[i] == '\r' || data[i] == '\t')) i++; };
skip_ws();
if (data[i] != '{') return false;
i++;
while (i < sz) {
skip_ws();
if (data[i] == '}') break;
if (data[i] == ',') { i++; skip_ws(); }
if (data[i] != '"') return false;
i++;
// Parse key (JSON string with escape handling)
std::string key;
while (i < sz && data[i] != '"') {
if (data[i] == '\\') {
i++;
if (i >= sz) return false;
switch (data[i]) {
case '"': key += '"'; break;
case '\\': key += '\\'; break;
case '/': key += '/'; break;
case 'n': key += '\n'; break;
case 'r': key += '\r'; break;
case 't': key += '\t'; break;
case 'u': {
// \uXXXX -> UTF-8
if (i + 4 >= sz) return false;
char hex[5] = {data[i+1], data[i+2], data[i+3], data[i+4], 0};
int cp = (int)strtol(hex, nullptr, 16);
i += 4;
if (cp < 0x80) { key += (char)cp; }
else if (cp < 0x800) { key += (char)(0xC0 | (cp >> 6)); key += (char)(0x80 | (cp & 0x3F)); }
else { key += (char)(0xE0 | (cp >> 12)); key += (char)(0x80 | ((cp >> 6) & 0x3F)); key += (char)(0x80 | (cp & 0x3F)); }
break;
}
default: key += data[i]; break;
}
} else {
key += data[i];
}
i++;
}
i++; // skip closing "
skip_ws();
if (data[i] != ':') return false;
i++;
skip_ws();
// Parse integer value
bool neg = false;
if (data[i] == '-') { neg = true; i++; }
int val = 0;
while (i < sz && data[i] >= '0' && data[i] <= '9') {
val = val * 10 + (data[i] - '0');
i++;
}
if (neg) val = -val;
vocab[key] = val;
}
return true;
}
// Load merges from merges.txt
static bool load_merges(const std::string &path, std::unordered_map<std::string, int> &merges) {
FILE *f = fopen(path.c_str(), "r");
if (!f) return false;
char line[512];
int rank = 0;
// Skip header line (#version: 0.2)
if (fgets(line, sizeof(line), f) == nullptr) { fclose(f); return false; }
while (fgets(line, sizeof(line), f)) {
int len = (int)strlen(line);
while (len > 0 && (line[len-1] == '\n' || line[len-1] == '\r')) line[--len] = '\0';
if (len == 0) continue;
merges[std::string(line)] = rank++;
}
fclose(f);
return true;
}
// Load tokenizer from directory (vocab.json + merges.txt)
static bool load_bpe_tokenizer(BPETokenizer *tok, const char *dir) {
build_byte_encoder(tok->byte2str);
std::string vocab_path = std::string(dir) + "/vocab.json";
std::string merges_path = std::string(dir) + "/merges.txt";
if (!load_vocab_json(vocab_path, tok->vocab)) {
fprintf(stderr, "ERROR: failed to load %s\n", vocab_path.c_str());
return false;
}
if (!load_merges(merges_path, tok->merges)) {
fprintf(stderr, "ERROR: failed to load %s\n", merges_path.c_str());
return false;
}
tok->n_vocab = (int)tok->vocab.size();
tok->eos_id = 151643; // <|endoftext|>
// Build reverse vocab (id -> token string)
tok->id_to_str.resize(tok->n_vocab);
for (auto &kv : tok->vocab) {
if (kv.second >= 0 && kv.second < tok->n_vocab)
tok->id_to_str[kv.second] = kv.first;
}
fprintf(stderr, "[BPE] Loaded: %d vocab, %d merges\n", tok->n_vocab, (int)tok->merges.size());
return true;
}
// Load tokenizer from GGUF KV (tokenizer.ggml.tokens + tokenizer.ggml.merges)
static bool load_bpe_from_gguf(BPETokenizer *tok, const char *gguf_path) {
build_byte_encoder(tok->byte2str);
struct gguf_init_params gp = { true, NULL };
struct gguf_context * ctx = gguf_init_from_file(gguf_path, gp);
if (!ctx) {
fprintf(stderr, "[BPE] failed to open %s\n", gguf_path);
return false;
}
int64_t tok_key = gguf_find_key(ctx, "tokenizer.ggml.tokens");
int64_t mrg_key = gguf_find_key(ctx, "tokenizer.ggml.merges");
if (tok_key < 0 || mrg_key < 0) {
fprintf(stderr, "[BPE] tokenizer not found in %s\n", gguf_path);
gguf_free(ctx);
return false;
}
int n_tokens = (int)gguf_get_arr_n(ctx, tok_key);
int n_merges = (int)gguf_get_arr_n(ctx, mrg_key);
for (int i = 0; i < n_tokens; i++) {
const char * s = gguf_get_arr_str(ctx, tok_key, (size_t)i);
tok->vocab[std::string(s)] = i;
}
for (int i = 0; i < n_merges; i++) {
const char * s = gguf_get_arr_str(ctx, mrg_key, (size_t)i);
tok->merges[std::string(s)] = i;
}
gguf_free(ctx);
tok->n_vocab = (int)tok->vocab.size();
tok->eos_id = 151643;
tok->id_to_str.resize(tok->n_vocab);
for (auto &kv : tok->vocab) {
if (kv.second >= 0 && kv.second < tok->n_vocab)
tok->id_to_str[kv.second] = kv.first;
}
fprintf(stderr, "[BPE] Loaded from GGUF: %d vocab, %d merges\n", tok->n_vocab, n_merges);
return true;
}
// Byte-level encode: raw text bytes -> GPT-2 BPE string
static std::string byte_level_encode(const BPETokenizer *tok, const std::string &text) {
std::string out;
for (unsigned char c : text) out += tok->byte2str[c];
return out;
}
// BPE merge algorithm
// Input: list of symbols (strings). Merges pairs by priority.
static std::vector<std::string> bpe_merge(const std::unordered_map<std::string, int> &merge_rank,
const std::vector<std::string> &symbols) {
if (symbols.size() <= 1) return symbols;
std::vector<std::string> work = symbols;
while (work.size() > 1) {
// Find the pair with lowest rank (highest priority)
int best_rank = INT_MAX;
int best_pos = -1;
for (int i = 0; i < (int)work.size() - 1; i++) {
std::string key = work[i] + " " + work[i + 1];
auto it = merge_rank.find(key);
if (it != merge_rank.end() && it->second < best_rank) {
best_rank = it->second;
best_pos = i;
}
}
if (best_pos < 0) break; // no more merges
// Merge the pair
std::string merged = work[best_pos] + work[best_pos + 1];
work[best_pos] = merged;
work.erase(work.begin() + best_pos + 1);
}
return work;
}
// Encode a single pre-tokenized chunk -> token ids
static void encode_chunk(const BPETokenizer *tok, const std::string &chunk,
std::vector<int> &ids) {
// Byte-level encode
std::string encoded = byte_level_encode(tok, chunk);
// Split into individual UTF-8 characters (each is a BPE symbol)
std::vector<std::string> symbols;
const char *s = encoded.c_str();
int len = (int)encoded.size();
int i = 0;
while (i < len) {
int adv;
utf8_codepoint(s + i, &adv);
symbols.push_back(std::string(s + i, adv));
i += adv;
}
// Apply BPE merges
std::vector<std::string> merged = bpe_merge(tok->merges, symbols);
// Look up in vocab
for (const auto &piece : merged) {
auto it = tok->vocab.find(piece);
if (it != tok->vocab.end()) {
ids.push_back(it->second);
} else {
// Fallback: encode each byte individually (should not happen with byte-level BPE)
fprintf(stderr, "[BPE] WARNING: unknown token '%s'\n", piece.c_str());
for (unsigned char c : piece) {
auto it2 = tok->vocab.find(std::string(1, c));
if (it2 != tok->vocab.end()) ids.push_back(it2->second);
}
}
}
}
// Full encode: text -> token ids
// Handles special tokens (<|endoftext|>) embedded in text.
// Appends EOS at end (post-processor behavior).
static std::vector<int> bpe_encode(const BPETokenizer *tok, const std::string &text,
bool add_eos = true) {
std::vector<int> ids;
const std::string special = "<|endoftext|>";
// Split on special tokens
size_t pos = 0;
while (pos < text.size()) {
size_t found = text.find(special, pos);
std::string segment = (found == std::string::npos)
? text.substr(pos) : text.substr(pos, found - pos);
// Pre-tokenize and encode the segment
if (!segment.empty()) {
auto chunks = gpt2_pre_tokenize(segment);
for (const auto &chunk : chunks) {
encode_chunk(tok, chunk, ids);
}
}
if (found == std::string::npos) break;
ids.push_back(tok->eos_id); // <|endoftext|>
pos = found + special.size();
}
if (add_eos) ids.push_back(tok->eos_id);
return ids;
}

293
otherarch/acestep/cond.h Normal file
View file

@ -0,0 +1,293 @@
// cond.h: ACEStep Condition Encoder via ggml
//
// Produces encoder_hidden_states [S_total, 2048] from (all arrays H-contiguous per token):
// - text_hidden [S_text, 1024] : from Qwen3-Embedding text encoder
// - lyric_embed [S_lyric, 1024] : from CPU vocab lookup of lyric tokens
// - timbre_feats [S_ref, 64] : reference audio features (optional)
//
// Internal pipeline (ggml notation [ne0, ne1]):
// text_hidden -> Linear(1024->2048) -> [2048, S_text]
// lyric_embed -> Linear(1024->2048)+bias -> 8L -> norm -> [2048, S_lyric]
// timbre_feats -> Linear(64->2048)+bias -> 4L -> norm -> take frame[0] -> [2048, 1]
// Pack: cat(lyric, timbre[0:1], text_proj) -> [2048, S_total]
#pragma once
#include "qwen3.h"
// Lyric/Timbre encoder configs
static Qwen3Config qwen3_lyric_config() {
return {
/*hidden_size*/ 2048,
/*intermediate_size*/ 6144,
/*n_heads*/ 16,
/*n_kv_heads*/ 8,
/*head_dim*/ 128,
/*n_layers*/ 8,
/*rope_theta*/ 1000000.0f,
/*rms_norm_eps*/ 1e-6f,
/*is_causal*/ false,
};
}
static Qwen3Config qwen3_timbre_config() {
return {
/*hidden_size*/ 2048,
/*intermediate_size*/ 6144,
/*n_heads*/ 16,
/*n_kv_heads*/ 8,
/*head_dim*/ 128,
/*n_layers*/ 4,
/*rope_theta*/ 1000000.0f,
/*rms_norm_eps*/ 1e-6f,
/*is_causal*/ false,
};
}
// Struct
struct CondGGML {
// Lyric encoder (8L, H=2048)
Qwen3Config lyric_cfg;
Qwen3Layer lyric_layers[8];
struct ggml_tensor * lyric_embed_w; // [2048, 1024] ggml = Linear(1024->2048)
struct ggml_tensor * lyric_embed_b; // [2048]
struct ggml_tensor * lyric_norm; // [2048]
// Timbre encoder (4L, H=2048)
Qwen3Config timbre_cfg;
Qwen3Layer timbre_layers[4];
struct ggml_tensor * timbre_embed_w; // [2048, 64] ggml = Linear(64->2048)
struct ggml_tensor * timbre_embed_b; // [2048]
struct ggml_tensor * timbre_norm; // [2048]
// Text projector: Linear(1024->2048) no bias
struct ggml_tensor * text_proj_w; // [2048, 1024] ggml
// Null condition embedding (for classifier-free guidance)
struct ggml_tensor * null_cond_emb; // [2048, 1, 1]
// Backend
ggml_backend_t backend;
ggml_backend_t cpu_backend;
ggml_backend_sched_t sched;
WeightCtx wctx;
};
// Init
static void cond_ggml_init_backend(CondGGML * m) {
BackendPair bp = backend_init("CondEncoder");
m->backend = bp.backend;
m->cpu_backend = bp.cpu_backend;
m->sched = backend_sched_new(bp, 8192);
}
// Load from ACEStep DiT GGUF
// gguf_path: path to the DiT .gguf file
// Tensors have prefix "encoder." for lyric/timbre, and "null_condition_emb"
static bool cond_ggml_load(CondGGML * m, const char * gguf_path) {
m->lyric_cfg = qwen3_lyric_config();
m->timbre_cfg = qwen3_timbre_config();
GGUFModel gf;
if (!gf_load(&gf, gguf_path)) {
fprintf(stderr, "[Load] FATAL: cannot load %s\n", gguf_path);
return false;
}
// Count tensors:
// lyric: embed_w(1) + embed_b(1) + 8 layers x 11(88) + norm(1) = 91
// timbre: embed_w(1) + embed_b(1) + 4 layers x 11(44) + norm(1) = 47
// text_proj(1) + null_cond(1) = 2
// Total: 140
int n_tensors = 91 + 47 + 2;
wctx_init(&m->wctx, n_tensors);
// Lyric encoder
m->lyric_embed_w = gf_load_tensor(&m->wctx, gf, "encoder.lyric_encoder.embed_tokens.weight");
m->lyric_embed_b = gf_load_tensor_f32(&m->wctx, gf, "encoder.lyric_encoder.embed_tokens.bias");
m->lyric_norm = gf_load_tensor_f32(&m->wctx, gf, "encoder.lyric_encoder.norm.weight");
for (int i = 0; i < m->lyric_cfg.n_layers; i++) {
char prefix[128];
snprintf(prefix, sizeof(prefix), "encoder.lyric_encoder.layers.%d", i);
qwen3_load_layer(&m->wctx, gf, &m->lyric_layers[i], prefix);
}
// Timbre encoder
m->timbre_embed_w = gf_load_tensor(&m->wctx, gf, "encoder.timbre_encoder.embed_tokens.weight");
m->timbre_embed_b = gf_load_tensor_f32(&m->wctx, gf, "encoder.timbre_encoder.embed_tokens.bias");
m->timbre_norm = gf_load_tensor_f32(&m->wctx, gf, "encoder.timbre_encoder.norm.weight");
for (int i = 0; i < m->timbre_cfg.n_layers; i++) {
char prefix[128];
snprintf(prefix, sizeof(prefix), "encoder.timbre_encoder.layers.%d", i);
qwen3_load_layer(&m->wctx, gf, &m->timbre_layers[i], prefix);
}
// Text projector + null condition
m->text_proj_w = gf_load_tensor(&m->wctx, gf, "encoder.text_projector.weight");
m->null_cond_emb = gf_load_tensor(&m->wctx, gf, "null_condition_emb");
if (!wctx_alloc(&m->wctx, m->backend)) {
gf_close(&gf);
return false;
}
gf_close(&gf);
fprintf(stderr, "[Load] CondEncoder: lyric(%dL), timbre(%dL), text_proj, null_cond\n",
m->lyric_cfg.n_layers, m->timbre_cfg.n_layers);
return true;
}
// Forward
//
// Inputs (CPU float arrays):
// text_hidden: [1024 * S_text] from text encoder (Qwen3-Embedding)
// lyric_embed: [1024 * S_lyric] from CPU vocab lookup of lyric tokens
// timbre_feats: [64 * S_ref] reference audio features (NULL if none)
// S_text, S_lyric, S_ref sequence lengths
//
// Output:
// enc_hidden: resized to [2048 * S_total] float
// *out_enc_S: total sequence length
//
// Layout: all arrays in ggml order (ne[0]=dim contiguous, then sequence)
static void cond_ggml_forward(CondGGML * m,
const float * text_hidden, int S_text,
const float * lyric_embed, int S_lyric,
const float * timbre_feats, int S_ref,
std::vector<float> & enc_hidden,
int * out_enc_S) {
int H = 2048;
bool has_timbre = (timbre_feats != NULL && S_ref > 0);
// Build graph
size_t ctx_size = 4096 * ggml_tensor_overhead() + ggml_graph_overhead();
struct ggml_init_params gp = { ctx_size, NULL, true };
struct ggml_context * ctx = ggml_init(gp);
struct ggml_cgraph * gf = ggml_new_graph_custom(ctx, 8192, false);
// Positions for lyric (bidirectional, 0..S_lyric-1)
struct ggml_tensor * lyric_pos = ggml_new_tensor_1d(ctx, GGML_TYPE_I32, S_lyric);
ggml_set_name(lyric_pos, "lyric_pos");
ggml_set_input(lyric_pos);
// Path 1: Lyric encoder
struct ggml_tensor * t_lyric_in = ggml_new_tensor_2d(ctx, GGML_TYPE_F32, 1024, S_lyric);
ggml_set_name(t_lyric_in, "lyric_in");
ggml_set_input(t_lyric_in);
// Linear embed: [1024, S_lyric] -> [2048, S_lyric]
struct ggml_tensor * lyric_h = qwen3_linear_bias(ctx, m->lyric_embed_w,
m->lyric_embed_b, t_lyric_in);
// 8 layers + final norm (bidirectional: mask=NULL)
lyric_h = qwen3_build_layers(ctx, m->lyric_cfg, m->lyric_layers, m->lyric_norm,
lyric_h, lyric_pos, NULL, S_lyric);
ggml_set_name(lyric_h, "lyric_out");
ggml_set_output(lyric_h);
// Path 2: Text projection
struct ggml_tensor * t_text_in = ggml_new_tensor_2d(ctx, GGML_TYPE_F32, 1024, S_text);
ggml_set_name(t_text_in, "text_in");
ggml_set_input(t_text_in);
// Linear: [1024, S_text] -> [2048, S_text]
struct ggml_tensor * text_proj = qwen3_linear(ctx, m->text_proj_w, t_text_in);
ggml_set_name(text_proj, "text_proj_out");
ggml_set_output(text_proj);
// Path 3: Timbre encoder (optional)
struct ggml_tensor * timbre_out = NULL;
struct ggml_tensor * t_timbre_in = NULL;
struct ggml_tensor * timbre_pos = NULL;
if (has_timbre) {
timbre_pos = ggml_new_tensor_1d(ctx, GGML_TYPE_I32, S_ref);
ggml_set_name(timbre_pos, "timbre_pos");
ggml_set_input(timbre_pos);
t_timbre_in = ggml_new_tensor_2d(ctx, GGML_TYPE_F32, 64, S_ref);
ggml_set_name(t_timbre_in, "timbre_in");
ggml_set_input(t_timbre_in);
// Linear embed: [64, S_ref] -> [2048, S_ref]
struct ggml_tensor * timbre_h = qwen3_linear_bias(ctx, m->timbre_embed_w,
m->timbre_embed_b, t_timbre_in);
// 4 layers + final norm
timbre_h = qwen3_build_layers(ctx, m->timbre_cfg, m->timbre_layers, m->timbre_norm,
timbre_h, timbre_pos, NULL, S_ref);
// Take first frame: [2048, S_ref] -> view [2048, 1]
timbre_out = ggml_view_2d(ctx, timbre_h, H, 1,
timbre_h->nb[1], 0);
ggml_set_name(timbre_out, "timbre_out");
ggml_set_output(timbre_out);
}
// Build forward
ggml_build_forward_expand(gf, lyric_h);
ggml_build_forward_expand(gf, text_proj);
if (timbre_out) ggml_build_forward_expand(gf, timbre_out);
// Allocate and set inputs
ggml_backend_sched_alloc_graph(m->sched, gf);
ggml_backend_tensor_set(t_lyric_in, lyric_embed, 0, 1024 * S_lyric * sizeof(float));
ggml_backend_tensor_set(t_text_in, text_hidden, 0, 1024 * S_text * sizeof(float));
// Lyric positions
{
std::vector<int> pos(S_lyric);
for (int i = 0; i < S_lyric; i++) pos[i] = i;
ggml_backend_tensor_set(lyric_pos, pos.data(), 0, S_lyric * sizeof(int));
}
if (has_timbre) {
ggml_backend_tensor_set(t_timbre_in, timbre_feats, 0, 64 * S_ref * sizeof(float));
std::vector<int> pos(S_ref);
for (int i = 0; i < S_ref; i++) pos[i] = i;
ggml_backend_tensor_set(timbre_pos, pos.data(), 0, S_ref * sizeof(int));
}
// Compute
ggml_backend_sched_graph_compute(m->sched, gf);
// Read outputs and pack on CPU
// Pack order: lyric, timbre[0:1], text_proj
int S_timbre_out = has_timbre ? 1 : 0;
int S_total = S_lyric + S_timbre_out + S_text;
enc_hidden.resize(H * S_total);
*out_enc_S = S_total;
int offset = 0;
// Lyric: [2048, S_lyric]
ggml_backend_tensor_get(lyric_h, enc_hidden.data() + offset * H,
0, H * S_lyric * sizeof(float));
offset += S_lyric;
// Timbre: [2048, 1]
if (timbre_out) {
ggml_backend_tensor_get(timbre_out, enc_hidden.data() + offset * H,
0, H * 1 * sizeof(float));
offset += 1;
}
// Text projection: [2048, S_text]
ggml_backend_tensor_get(text_proj, enc_hidden.data() + offset * H,
0, H * S_text * sizeof(float));
offset += S_text;
fprintf(stderr, "[Encode] Packed: lyric=%d + timbre=%d + text=%d = %d tokens\n",
S_lyric, S_timbre_out, S_text, S_total);
ggml_backend_sched_reset(m->sched);
ggml_free(ctx);
}
// Free
static void cond_ggml_free(CondGGML * m) {
if (m->sched) ggml_backend_sched_free(m->sched);
if (m->backend && m->backend != m->cpu_backend) ggml_backend_free(m->backend);
if (m->cpu_backend) ggml_backend_free(m->cpu_backend);
wctx_free(&m->wctx);
*m = {};
}

115
otherarch/acestep/debug.h Normal file
View file

@ -0,0 +1,115 @@
#pragma once
// debug.h: tensor dump/compare utilities for Python vs GGML validation
// Dumps raw f32 arrays to binary files. Both backends convert to f32 before dump.
// File format: [int32 ndims] [int32 dim0] [int32 dim1] ... [float data...]
#include <cstdio>
#include <cstdint>
#include <cmath>
#include <cstring>
#include <string>
#include <vector>
struct DebugDumper {
char dir[512];
bool enabled;
};
static void debug_init(DebugDumper *d, const char *dir) {
d->enabled = (dir != nullptr);
if (d->enabled)
snprintf(d->dir, sizeof(d->dir), "%s", dir);
}
// Dump f32 tensor to binary file
// Format: [ndims:i32] [shape:i32 x ndims] [data:f32 x numel]
static void debug_dump(const DebugDumper *d, const char *name,
const float *data, const int *shape, int ndims) {
if (!d->enabled) return;
char path[1024];
snprintf(path, sizeof(path), "%s/%s.bin", d->dir, name);
int numel = 1;
for (int i = 0; i < ndims; i++) numel *= shape[i];
FILE *f = fopen(path, "wb");
if (!f) { fprintf(stderr, "[Debug] cannot write %s\n", path); return; }
fwrite(&ndims, sizeof(int32_t), 1, f);
fwrite(shape, sizeof(int32_t), ndims, f);
fwrite(data, sizeof(float), numel, f);
fclose(f);
// Print first 4 values for quick sanity check
fprintf(stderr, "[Debug] %s: [", name);
for (int i = 0; i < ndims; i++) fprintf(stderr, "%s%d", i ? ", " : "", shape[i]);
fprintf(stderr, "] first4:");
for (int i = 0; i < 4 && i < numel; i++) fprintf(stderr, " %.6f", data[i]);
fprintf(stderr, "\n");
}
// Convenience: dump 2D tensor [rows, cols]
static void debug_dump_2d(const DebugDumper *d, const char *name,
const float *data, int dim0, int dim1) {
int shape[2] = {dim0, dim1};
debug_dump(d, name, data, shape, 2);
}
// Convenience: dump 1D tensor [n]
static void debug_dump_1d(const DebugDumper *d, const char *name,
const float *data, int n) {
debug_dump(d, name, data, &n, 1);
}
// Load a debug dump, returns data and fills shape/ndims
static std::vector<float> debug_load(const char *path, std::vector<int> &shape) {
FILE *f = fopen(path, "rb");
if (!f) { fprintf(stderr, "[Debug] cannot read %s\n", path); return {}; }
int32_t ndims;
fread(&ndims, sizeof(int32_t), 1, f);
shape.resize(ndims);
fread(shape.data(), sizeof(int32_t), ndims, f);
int numel = 1;
for (int i = 0; i < ndims; i++) numel *= shape[i];
std::vector<float> data(numel);
fread(data.data(), sizeof(float), numel, f);
fclose(f);
return data;
}
// Cosine similarity between two f32 arrays
static double debug_cosine_sim(const float *a, const float *b, int n) {
double dot = 0, na = 0, nb = 0;
for (int i = 0; i < n; i++) {
dot += (double)a[i] * (double)b[i];
na += (double)a[i] * (double)a[i];
nb += (double)b[i] * (double)b[i];
}
if (na < 1e-30 || nb < 1e-30) return 0.0;
return dot / (sqrt(na) * sqrt(nb));
}
// Max absolute error
static double debug_max_abs_err(const float *a, const float *b, int n) {
double mx = 0;
for (int i = 0; i < n; i++) {
double d = fabs((double)a[i] - (double)b[i]);
if (d > mx) mx = d;
}
return mx;
}
// Mean absolute error
static double debug_mean_abs_err(const float *a, const float *b, int n) {
double sum = 0;
for (int i = 0; i < n; i++) {
sum += fabs((double)a[i] - (double)b[i]);
}
return sum / n;
}

View file

@ -0,0 +1,581 @@
// dit.cpp: ACEStep music generation via ggml (dit-vae binary)
//
// Usage: ./dit-vae [options]
// See --help for full option list.
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <vector>
#include <chrono>
#include <random>
#include "ggml.h"
#include "ggml-backend.h"
#include "./dit.h"
#include "./vae.h"
#include "./qwen3.h"
#include "./tokenizer.h"
#include "./cond.h"
#include "./bpe.h"
#include "./debug.h"
#include "./request.h"
struct Timer {
std::chrono::steady_clock::time_point t;
Timer() : t(std::chrono::steady_clock::now()) {}
double ms() const {
return std::chrono::duration<double, std::milli>(
std::chrono::steady_clock::now() - t).count();
}
void reset() { t = std::chrono::steady_clock::now(); }
};
// Minimal WAV writer (16-bit PCM stereo)
static bool write_wav(const char * path, const float * audio, int T_audio, int sr) {
FILE * f = fopen(path, "wb");
if (!f) return false;
int n_channels = 2;
int bits = 16;
int byte_rate = sr * n_channels * (bits / 8);
int block_align = n_channels * (bits / 8);
int data_size = T_audio * n_channels * (bits / 8);
int file_size = 36 + data_size;
fwrite("RIFF", 1, 4, f);
fwrite(&file_size, 4, 1, f);
fwrite("WAVE", 1, 4, f);
fwrite("fmt ", 1, 4, f);
int fmt_size = 16; fwrite(&fmt_size, 4, 1, f);
short audio_fmt = 1; fwrite(&audio_fmt, 2, 1, f);
short nc = (short)n_channels; fwrite(&nc, 2, 1, f);
fwrite(&sr, 4, 1, f);
fwrite(&byte_rate, 4, 1, f);
short ba = (short)block_align; fwrite(&ba, 2, 1, f);
short bp = (short)bits; fwrite(&bp, 2, 1, f);
fwrite("data", 1, 4, f);
fwrite(&data_size, 4, 1, f);
for (int t = 0; t < T_audio; t++) {
for (int c = 0; c < 2; c++) {
float s = audio[c * T_audio + t];
s = s < -1.0f ? -1.0f : (s > 1.0f ? 1.0f : s);
short v = (short)(s * 32767.0f);
fwrite(&v, 2, 1, f);
}
}
fclose(f);
return true;
}
static void print_usage(const char * prog) {
fprintf(stderr,
"Usage: %s --request <json...> --text-encoder <gguf> --dit <gguf> --vae <gguf> [options]\n\n"
"Required:\n"
" --request <json...> One or more request JSONs (from ace-qwen3 --request)\n"
" --text-encoder <gguf> Text encoder GGUF file\n"
" --dit <gguf> DiT GGUF file (from convert.py)\n"
" --vae <gguf> VAE GGUF file\n\n"
"Batch:\n"
" --batch <n> DiT variations per request (default: 1, max 9)\n\n"
"Audio:\n"
" --noise-file <path> Load noise from bf16 file (Philox RNG dump, batch=1 only)\n\n"
"VAE tiling (memory control):\n"
" --vae-chunk <n> Latent frames per tile (default: 256)\n"
" --vae-overlap <n> Overlap frames per side (default: 64)\n\n"
"Output naming: input.json -> input0.wav, input1.wav, ... (last digit = batch index)\n\n"
"Debug:\n"
" --dump <dir> Dump intermediate tensors\n", prog);
}
// Parse comma-separated codes string into vector
static std::vector<int> parse_codes_string(const std::string & s) {
std::vector<int> codes;
if (s.empty()) return codes;
const char * p = s.c_str();
while (*p) {
while (*p == ',' || *p == ' ') p++;
if (!*p) break;
codes.push_back(atoi(p));
while (*p && *p != ',') p++;
}
return codes;
}
int main(int argc, char ** argv) {
if (argc < 2) { print_usage(argv[0]); return 1; }
std::vector<const char *> request_paths;
const char * text_enc_gguf = NULL;
const char * dit_gguf = NULL;
const char * vae_gguf = NULL;
const char * dump_dir = NULL;
const char * noise_file = NULL;
int batch_n = 1;
int vae_chunk = 256;
int vae_overlap = 64;
for (int i = 1; i < argc; i++) {
if (strcmp(argv[i], "--request") == 0) {
// Collect all following non-option args
while (i+1 < argc && argv[i+1][0] != '-')
request_paths.push_back(argv[++i]);
}
else if (strcmp(argv[i], "--text-encoder") == 0 && i+1 < argc) text_enc_gguf = argv[++i];
else if (strcmp(argv[i], "--dit") == 0 && i+1 < argc) dit_gguf = argv[++i];
else if (strcmp(argv[i], "--vae") == 0 && i+1 < argc) vae_gguf = argv[++i];
else if (strcmp(argv[i], "--noise-file") == 0 && i+1 < argc) noise_file = argv[++i];
else if (strcmp(argv[i], "--dump") == 0 && i+1 < argc) dump_dir = argv[++i];
else if (strcmp(argv[i], "--batch") == 0 && i+1 < argc) batch_n = atoi(argv[++i]);
else if (strcmp(argv[i], "--vae-chunk") == 0 && i+1 < argc) vae_chunk = atoi(argv[++i]);
else if (strcmp(argv[i], "--vae-overlap") == 0 && i+1 < argc) vae_overlap = atoi(argv[++i]);
else if (strcmp(argv[i], "--help") == 0 || strcmp(argv[i], "-h") == 0) {
print_usage(argv[0]); return 0;
} else {
fprintf(stderr, "Unknown option: %s\n", argv[i]);
print_usage(argv[0]); return 1;
}
}
if (request_paths.empty()) {
fprintf(stderr, "ERROR: --request required\n");
print_usage(argv[0]); return 1;
}
if (batch_n < 1 || batch_n > 9) {
fprintf(stderr, "ERROR: --batch must be 1..9\n"); return 1;
}
if (!dit_gguf) {
fprintf(stderr, "ERROR: --dit required\n");
print_usage(argv[0]); return 1;
}
if (!text_enc_gguf) {
fprintf(stderr, "ERROR: --text-encoder required\n");
print_usage(argv[0]); return 1;
}
const int FRAMES_PER_SECOND = 25;
DebugDumper dbg;
debug_init(&dbg, dump_dir);
Timer timer;
DiTGGMLConfig cfg;
DiTGGML model = {};
// Load DiT model (once for all requests)
dit_ggml_init_backend(&model);
fprintf(stderr, "[Load] Backend init: %.1f ms\n", timer.ms());
timer.reset();
if (!dit_ggml_load(&model, dit_gguf, cfg)) {
fprintf(stderr, "FATAL: failed to load DiT model\n");
return 1;
}
fprintf(stderr, "[Load] DiT weight load: %.1f ms\n", timer.ms());
// Read DiT GGUF metadata + silence_latent tensor (once)
bool is_turbo = false;
std::vector<float> silence_full; // [15000, 64] f32
{
GGUFModel gf = {};
if (gf_load(&gf, dit_gguf)) {
is_turbo = gf_get_bool(gf, "acestep.is_turbo");
const void * sl_data = gf_get_data(gf, "silence_latent");
if (sl_data) {
silence_full.resize(15000 * 64);
memcpy(silence_full.data(), sl_data, 15000 * 64 * sizeof(float));
fprintf(stderr, "[Load] silence_latent: [15000, 64] from GGUF\n");
} else {
fprintf(stderr, "FATAL: silence_latent tensor not found in %s\n", dit_gguf);
gf_close(&gf);
dit_ggml_free(&model);
return 1;
}
gf_close(&gf);
} else {
fprintf(stderr, "FATAL: cannot reopen %s for metadata\n", dit_gguf);
dit_ggml_free(&model);
return 1;
}
}
int Oc = cfg.out_channels; // 64
int ctx_ch = cfg.in_channels - Oc; // 128
// Load VAE model (once for all requests)
VAEGGML vae = {};
bool have_vae = false;
if (vae_gguf) {
timer.reset();
vae_ggml_load(&vae, vae_gguf);
fprintf(stderr, "[Load] VAE weights: %.1f ms\n", timer.ms());
have_vae = true;
}
// Process each request
for (int ri = 0; ri < (int)request_paths.size(); ri++) {
const char * rpath = request_paths[ri];
fprintf(stderr, "[Request %d/%d] %s (batch=%d)\n",
ri + 1, (int)request_paths.size(), rpath, batch_n);
// Compute output basename: strip .json suffix
std::string basename(rpath);
{
size_t dot = basename.rfind(".json");
if (dot != std::string::npos)
basename = basename.substr(0, dot);
}
// Parse request JSON
AceRequest req;
request_init(&req);
if (!request_parse(&req, rpath)) {
fprintf(stderr, "ERROR: failed to parse %s, skipping\n", rpath);
continue;
}
if (req.caption.empty()) {
fprintf(stderr, "ERROR: caption is empty in %s, skipping\n", rpath);
continue;
}
// Extract params
const char * caption = req.caption.c_str();
const char * lyrics = req.lyrics.empty() ? "[Instrumental]" : req.lyrics.c_str();
char bpm_str[16] = "N/A";
if (req.bpm > 0) snprintf(bpm_str, sizeof(bpm_str), "%d", req.bpm);
const char * bpm = bpm_str;
const char * keyscale = req.keyscale.empty() ? "N/A" : req.keyscale.c_str();
const char * timesig = req.timesignature.empty() ? "N/A" : req.timesignature.c_str();
const char * language = req.vocal_language.empty() ? "en" : req.vocal_language.c_str();
float duration = req.duration > 0 ? req.duration : 120.0f;
int seed = req.seed;
int num_steps = req.inference_steps > 0 ? req.inference_steps : 8;
float guidance_scale = req.guidance_scale > 0 ? req.guidance_scale : 7.0f;
float shift = req.shift > 0 ? req.shift : 1.0f;
if (is_turbo && guidance_scale > 1.0f) {
fprintf(stderr, "[Pipeline] WARNING: turbo model, forcing guidance_scale=1.0 (was %.1f)\n",
guidance_scale);
guidance_scale = 1.0f;
}
if (seed < 0) {
std::random_device rd;
seed = (int)(rd() & 0x7FFFFFFF);
}
fprintf(stderr, "[Pipeline] seed=%d, steps=%d, guidance=%.1f, shift=%.1f, duration=%.1fs\n",
seed, num_steps, guidance_scale, shift, duration);
// Parse audio codes from request
std::vector<int> codes_vec = parse_codes_string(req.audio_codes);
if (!codes_vec.empty())
fprintf(stderr, "[Pipeline] %zu audio codes (%.1fs @ 5Hz)\n",
codes_vec.size(), (float)codes_vec.size() / 5.0f);
// Build schedule: t_i = shift * t / (1 + (shift-1)*t) where t = 1 - i/steps
std::vector<float> schedule(num_steps);
for (int i = 0; i < num_steps; i++) {
float t = 1.0f - (float)i / (float)num_steps;
schedule[i] = shift * t / (1.0f + (shift - 1.0f) * t);
}
// T = number of 25Hz latent frames for DiT
// When audio codes are present, T is determined by the codes.
// Otherwise, T is derived from the requested duration.
int T = codes_vec.empty()
? (int)(duration * FRAMES_PER_SECOND)
: (int)codes_vec.size() * 5;
T = ((T + cfg.patch_size - 1) / cfg.patch_size) * cfg.patch_size;
int S = T / cfg.patch_size;
int enc_S = 0;
fprintf(stderr, "[Pipeline] T=%d, S=%d\n", T, S);
if (T > 15000) {
fprintf(stderr, "ERROR: T=%d exceeds silence_latent max 15000, skipping\n", T);
continue;
}
// Text encoding
// 1. Load BPE tokenizer
timer.reset();
BPETokenizer tok;
if (!load_bpe_from_gguf(&tok, text_enc_gguf)) {
fprintf(stderr, "FATAL: failed to load tokenizer from %s\n", text_enc_gguf);
dit_ggml_free(&model);
if (have_vae) vae_ggml_free(&vae);
return 1;
}
fprintf(stderr, "[Load] BPE tokenizer: %.1f ms\n", timer.ms());
// 2. Build formatted prompts
const char * instruction = "Generate audio semantic tokens based on the given conditions:";
char metas[512];
snprintf(metas, sizeof(metas),
"- bpm: %s\n- timesignature: %s\n- keyscale: %s\n- duration: %d seconds\n",
bpm, timesig, keyscale, (int)duration);
std::string text_str = std::string("# Instruction\n")
+ instruction + "\n\n"
+ "# Caption\n" + caption + "\n\n"
+ "# Metas\n" + metas + "<|endoftext|>\n";
bool instrumental = (strcmp(lyrics, "[Instrumental]") == 0 || strcmp(lyrics, "[instrumental]") == 0);
std::string lyric_str = std::string("# Languages\n") + language + "\n\n# Lyric\n"
+ (instrumental ? "[Instrumental]" : lyrics) + "<|endoftext|>";
// 3. Tokenize
auto text_ids = bpe_encode(&tok, text_str.c_str(), true);
auto lyric_ids = bpe_encode(&tok, lyric_str.c_str(), true);
int S_text = (int)text_ids.size();
int S_lyric = (int)lyric_ids.size();
fprintf(stderr, "[Pipeline] caption: %d tokens, lyrics: %d tokens\n", S_text, S_lyric);
// 4. Text encoder forward (caption only)
timer.reset();
Qwen3GGML text_enc = {};
qwen3_init_backend(&text_enc);
if (!qwen3_load_text_encoder(&text_enc, text_enc_gguf)) {
fprintf(stderr, "FATAL: failed to load text encoder\n");
dit_ggml_free(&model);
if (have_vae) vae_ggml_free(&vae);
return 1;
}
fprintf(stderr, "[Load] TextEncoder: %.1f ms\n", timer.ms());
int H_text = text_enc.cfg.hidden_size; // 1024
std::vector<float> text_hidden(H_text * S_text);
timer.reset();
qwen3_forward(&text_enc, text_ids.data(), S_text, text_hidden.data());
fprintf(stderr, "[Encode] TextEncoder (%d tokens): %.1f ms\n", S_text, timer.ms());
debug_dump_2d(&dbg, "text_hidden", text_hidden.data(), S_text, H_text);
// 5. Lyric embedding (CPU vocab lookup from text encoder embed table)
timer.reset();
std::vector<float> lyric_embed(H_text * S_lyric);
{
GGUFModel gf_te = {};
if (!gf_load(&gf_te, text_enc_gguf)) {
fprintf(stderr, "FATAL: cannot reopen text encoder GGUF for lyric embed\n");
dit_ggml_free(&model);
if (have_vae) vae_ggml_free(&vae);
return 1;
}
const void * embed_data = gf_get_data(gf_te, "embed_tokens.weight");
if (!embed_data) {
fprintf(stderr, "FATAL: embed_tokens.weight not found\n");
gf_close(&gf_te);
dit_ggml_free(&model);
if (have_vae) vae_ggml_free(&vae);
return 1;
}
qwen3_cpu_embed_lookup(embed_data, H_text,
lyric_ids.data(), S_lyric,
lyric_embed.data());
gf_close(&gf_te);
}
fprintf(stderr, "[Encode] Lyric vocab lookup (%d tokens): %.1f ms\n", S_lyric, timer.ms());
debug_dump_2d(&dbg, "lyric_embed", lyric_embed.data(), S_lyric, H_text);
// 6. Condition encoder forward
timer.reset();
CondGGML cond = {};
cond_ggml_init_backend(&cond);
if (!cond_ggml_load(&cond, dit_gguf)) {
fprintf(stderr, "FATAL: failed to load condition encoder\n");
dit_ggml_free(&model);
if (have_vae) vae_ggml_free(&vae);
return 1;
}
fprintf(stderr, "[Load] ConditionEncoder: %.1f ms\n", timer.ms());
// Silence feats for timbre input: first 750 frames (30s @ 25Hz)
const int S_ref = 750;
std::vector<float> silence_feats(S_ref * 64);
memcpy(silence_feats.data(), silence_full.data(), S_ref * 64 * sizeof(float));
timer.reset();
std::vector<float> enc_hidden;
cond_ggml_forward(&cond, text_hidden.data(), S_text,
lyric_embed.data(), S_lyric,
silence_feats.data(), S_ref,
enc_hidden, &enc_S);
fprintf(stderr, "[Encode] ConditionEncoder: %.1f ms, enc_S=%d\n", timer.ms(), enc_S);
qwen3_free(&text_enc);
cond_ggml_free(&cond);
debug_dump_2d(&dbg, "enc_hidden", enc_hidden.data(), enc_S, 2048);
// Context building
// Silence latent for this T
std::vector<float> silence(Oc * T);
memcpy(silence.data(), silence_full.data(), (size_t)(Oc * T) * sizeof(float));
// Decode audio codes if provided
int decoded_T = 0;
std::vector<float> decoded_latents;
if (!codes_vec.empty()) {
timer.reset();
DetokGGML detok = {};
if (!detok_ggml_load(&detok, dit_gguf, model.backend, model.cpu_backend)) {
fprintf(stderr, "FATAL: failed to load detokenizer\n");
dit_ggml_free(&model);
if (have_vae) vae_ggml_free(&vae);
return 1;
}
fprintf(stderr, "[Load] Detokenizer: %.1f ms\n", timer.ms());
int T_5Hz = (int)codes_vec.size();
int T_25Hz_codes = T_5Hz * 5;
decoded_latents.resize(T_25Hz_codes * Oc);
timer.reset();
int ret = detok_ggml_decode(&detok, codes_vec.data(), T_5Hz, decoded_latents.data());
if (ret < 0) {
fprintf(stderr, "FATAL: detokenizer decode failed\n");
dit_ggml_free(&model);
if (have_vae) vae_ggml_free(&vae);
return 1;
}
fprintf(stderr, "[Context] Detokenizer: %.1f ms\n", timer.ms());
decoded_T = T_25Hz_codes < T ? T_25Hz_codes : T;
debug_dump_2d(&dbg, "detok_output", decoded_latents.data(), T_25Hz_codes, Oc);
detok_ggml_free(&detok);
}
// Build single context: [T, ctx_ch] = src_latents[64] + mask_ones[64]
std::vector<float> context_single(T * ctx_ch);
for (int t = 0; t < T; t++) {
const float * src = (t < decoded_T)
? decoded_latents.data() + t * Oc
: silence.data() + t * Oc;
for (int c = 0; c < Oc; c++)
context_single[t * ctx_ch + c] = src[c];
for (int c = 0; c < Oc; c++)
context_single[t * ctx_ch + Oc + c] = 1.0f;
}
// Replicate context for N batch samples (all identical)
std::vector<float> context(batch_n * T * ctx_ch);
for (int b = 0; b < batch_n; b++)
memcpy(context.data() + b * T * ctx_ch, context_single.data(),
T * ctx_ch * sizeof(float));
// Generate N noise samples
std::vector<float> noise(batch_n * Oc * T);
if (noise_file && batch_n == 1) {
// Load pre-generated Philox noise from bf16 file (batch=1 only)
FILE * nf = fopen(noise_file, "rb");
if (!nf) { fprintf(stderr, "FATAL: cannot open noise file %s\n", noise_file); return 1; }
fseek(nf, 0, SEEK_END);
int total_bf16 = (int)(ftell(nf) / sizeof(uint16_t));
fseek(nf, 0, SEEK_SET);
int T_file = total_bf16 / Oc;
if (T_file < T) {
fprintf(stderr, "FATAL: noise file too short: T_file=%d < T=%d\n", T_file, T);
fclose(nf); return 1;
}
std::vector<uint16_t> bf16_all(Oc * T);
if (fread(bf16_all.data(), sizeof(uint16_t), Oc * T, nf) != (size_t)(Oc * T)) {
fprintf(stderr, "FATAL: noise file read error\n");
fclose(nf); return 1;
}
fclose(nf);
for (int i = 0; i < Oc * T; i++) {
uint32_t w = (uint32_t)bf16_all[i] << 16;
float v; memcpy(&v, &w, 4);
noise[i] = v;
}
fprintf(stderr, "[Context] loaded noise from %s: [%d, %d] bf16\n",
noise_file, T, Oc);
} else {
// Generate N noise samples with seeds: seed, seed+1, ..., seed+N-1
for (int b = 0; b < batch_n; b++) {
std::mt19937 rng(seed + b);
std::normal_distribution<float> normal(0.0f, 1.0f);
float * dst = noise.data() + b * Oc * T;
for (int i = 0; i < Oc * T; i++)
dst[i] = normal(rng);
fprintf(stderr, "[Context Batch%d] noise seed=%d\n", b, seed + b);
}
}
// DiT Generate
std::vector<float> output(batch_n * Oc * T);
// Debug dumps (sample 0)
debug_dump_2d(&dbg, "noise", noise.data(), T, Oc);
debug_dump_2d(&dbg, "context", context.data(), T, ctx_ch);
fprintf(stderr, "[DiT] Starting: T=%d, S=%d, enc_S=%d, steps=%d, batch=%d\n",
T, S, enc_S, num_steps, batch_n);
timer.reset();
dit_ggml_generate(&model, noise.data(), context.data(), enc_hidden.data(),
enc_S, T, batch_n, num_steps, schedule.data(), output.data(),
guidance_scale, &dbg);
fprintf(stderr, "[DiT] Total generation: %.1f ms (%.1f ms/sample)\n",
timer.ms(), timer.ms() / batch_n);
debug_dump_2d(&dbg, "dit_output", output.data(), T, Oc);
// VAE Decode + Write WAVs
if (have_vae) {
int T_latent = T;
int T_audio_max = T_latent * 1920;
std::vector<float> audio(2 * T_audio_max);
for (int b = 0; b < batch_n; b++) {
float * dit_out = output.data() + b * Oc * T;
timer.reset();
int T_audio = vae_ggml_decode_tiled(&vae, dit_out, T_latent, audio.data(),
T_audio_max, vae_chunk, vae_overlap);
if (T_audio < 0) {
fprintf(stderr, "[VAE Batch%d] ERROR: decode failed\n", b);
continue;
}
fprintf(stderr, "[VAE Batch%d] Decode: %.1f ms\n", b, timer.ms());
// Peak normalization to -1.0 dB
{
float peak = 0.0f;
int n_samples = 2 * T_audio;
for (int i = 0; i < n_samples; i++) {
float a = audio[i] < 0 ? -audio[i] : audio[i];
if (a > peak) peak = a;
}
if (peak > 1e-6f) {
const float target_amp = powf(10.0f, -1.0f / 20.0f);
float gain = target_amp / peak;
for (int i = 0; i < n_samples; i++)
audio[i] *= gain;
}
}
// Write WAV: basename + batch_index + .wav
char wav_path[1024];
snprintf(wav_path, sizeof(wav_path), "%s%d.wav", basename.c_str(), b);
if (b == 0) debug_dump_2d(&dbg, "vae_audio", audio.data(), 2, T_audio);
if (write_wav(wav_path, audio.data(), T_audio, 48000)) {
fprintf(stderr, "[VAE Batch%d] Wrote %s: %d samples (%.2fs @ 48kHz stereo)\n",
b, wav_path, T_audio, (float)T_audio / 48000.0f);
} else {
fprintf(stderr, "[VAE Batch%d] FATAL: failed to write %s\n", b, wav_path);
}
}
}
fprintf(stderr, "[Request %d/%d] Done\n", ri + 1, (int)request_paths.size());
}
if (have_vae) vae_ggml_free(&vae);
dit_ggml_free(&model);
fprintf(stderr, "[Pipeline] All done\n");
return 0;
}

1400
otherarch/acestep/dit.h Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,343 @@
#pragma once
// gguf_weights.h: load model weights from GGUF files
//
// GGUF weight loader for all model components (LM, DiT, CondEncoder, TextEncoder, Detokenizer, VAE).
// All components use GGUF bf16 files generated by convert.py.
//
// Usage:
// GGUFModel gf;
// if (!gf_load(&gf, "model.gguf")) { error; }
// WeightCtx wctx;
// wctx_init(&wctx, n_tensors);
// ggml_tensor * w = gf_load_tensor(&wctx, gf, "layer.0.weight");
// wctx_alloc(&wctx, backend);
// gf_close(&gf); // safe after wctx_alloc copied data to GPU
#include "weight_ctx.h"
#include "gguf.h"
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <string>
#ifdef _WIN32
#include <windows.h>
#else
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <unistd.h>
#endif
struct GGUFModel {
struct gguf_context * gguf; // parsed header (KV + tensor metadata)
struct ggml_context * meta; // tensor descriptors (no data)
uint8_t * mapping; // mmapped file
size_t file_size;
size_t data_offset; // gguf_get_data_offset(gguf)
#ifdef _WIN32
HANDLE fh;
HANDLE mh;
#else
int fd;
#endif
};
static void gf_close(GGUFModel * gf) {
if (gf->gguf) gguf_free(gf->gguf);
if (gf->meta) ggml_free(gf->meta);
#ifdef _WIN32
if (gf->mapping) UnmapViewOfFile(gf->mapping);
if (gf->mh) CloseHandle(gf->mh);
if (gf->fh && gf->fh != INVALID_HANDLE_VALUE) CloseHandle(gf->fh);
#else
if (gf->mapping) munmap(gf->mapping, gf->file_size);
if (gf->fd >= 0) close(gf->fd);
#endif
*gf = {};
}
static bool gf_load(GGUFModel * gf, const char * path) {
*gf = {};
// mmap the file
#ifdef _WIN32
gf->fh = CreateFileA(path, GENERIC_READ, FILE_SHARE_READ, NULL,
OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (gf->fh == INVALID_HANDLE_VALUE) {
fprintf(stderr, "[GGUF] cannot open %s\n", path);
return false;
}
LARGE_INTEGER li;
GetFileSizeEx(gf->fh, &li);
gf->file_size = (size_t)li.QuadPart;
gf->mh = CreateFileMappingA(gf->fh, NULL, PAGE_READONLY, 0, 0, NULL);
if (!gf->mh) {
CloseHandle(gf->fh);
fprintf(stderr, "[GGUF] CreateFileMapping failed %s\n", path);
return false;
}
gf->mapping = (uint8_t *)MapViewOfFile(gf->mh, FILE_MAP_READ, 0, 0, 0);
if (!gf->mapping) {
CloseHandle(gf->mh);
CloseHandle(gf->fh);
fprintf(stderr, "[GGUF] MapViewOfFile failed %s\n", path);
return false;
}
#else
gf->fd = open(path, O_RDONLY);
if (gf->fd < 0) {
fprintf(stderr, "[GGUF] cannot open %s\n", path);
return false;
}
struct stat sb;
fstat(gf->fd, &sb);
gf->file_size = (size_t)sb.st_size;
gf->mapping = (uint8_t *)mmap(NULL, gf->file_size, PROT_READ, MAP_PRIVATE, gf->fd, 0);
if (gf->mapping == MAP_FAILED) {
close(gf->fd);
gf->mapping = NULL;
fprintf(stderr, "[GGUF] mmap failed %s\n", path);
return false;
}
#endif
// Parse GGUF header, create tensor metadata context
struct ggml_context * meta = NULL;
struct gguf_init_params params = { /*no_alloc=*/ true, /*ctx=*/ &meta };
gf->gguf = gguf_init_from_file(path, params);
if (!gf->gguf) {
fprintf(stderr, "[GGUF] failed to parse %s\n", path);
gf_close(gf);
return false;
}
gf->meta = meta;
gf->data_offset = gguf_get_data_offset(gf->gguf);
int64_t n = gguf_get_n_tensors(gf->gguf);
fprintf(stderr, "[GGUF] %s: %lld tensors, data at offset %zu\n",
path, (long long)n, gf->data_offset);
return true;
}
// Load a tensor from GGUF into the weight context.
// Returns ggml_tensor (not yet backed by memory; call wctx_alloc after all loads).
// Tensor shapes are already in ggml order (ne[0]=innermost).
static struct ggml_tensor * gf_load_tensor(
WeightCtx * wctx,
const GGUFModel & gf,
const std::string & name,
const int64_t * shape_override = nullptr,
int n_dims_override = 0) {
int64_t idx = gguf_find_tensor(gf.gguf, name.c_str());
if (idx < 0) {
fprintf(stderr, "[GGUF] FATAL: tensor '%s' not found\n", name.c_str());
exit(1);
}
// Get metadata from the context populated by gguf_init_from_file
struct ggml_tensor * src = ggml_get_tensor(gf.meta, name.c_str());
if (!src) {
fprintf(stderr, "[GGUF] FATAL: tensor '%s' not in meta context\n", name.c_str());
exit(1);
}
int n_dims;
int64_t ne[4] = {1, 1, 1, 1};
if (shape_override && n_dims_override > 0) {
n_dims = n_dims_override;
for (int i = 0; i < n_dims; i++) ne[i] = shape_override[i];
} else {
n_dims = ggml_n_dims(src);
for (int i = 0; i < n_dims; i++) ne[i] = src->ne[i];
}
struct ggml_tensor * tensor = ggml_new_tensor(wctx->ctx, src->type, n_dims, ne);
ggml_set_name(tensor, name.c_str());
size_t offset = gguf_get_tensor_offset(gf.gguf, idx);
const void * data = gf.mapping + gf.data_offset + offset;
size_t nbytes = ggml_nbytes(src);
wctx->pending.push_back({tensor, data, nbytes, 0});
return tensor;
}
// Try to load, returns nullptr if not found (no exit)
static struct ggml_tensor * gf_try_load_tensor(
WeightCtx * wctx,
const GGUFModel & gf,
const std::string & name) {
int64_t idx = gguf_find_tensor(gf.gguf, name.c_str());
if (idx < 0) return nullptr;
return gf_load_tensor(wctx, gf, name);
}
// Load tensor, converting to F32 at load time (eliminates runtime cast nodes).
// Best for small tensors: norms [H], QK-norms [D], scale_shift_table [H,6], biases.
static struct ggml_tensor * gf_load_tensor_f32(
WeightCtx * wctx,
const GGUFModel & gf,
const std::string & name) {
int64_t idx = gguf_find_tensor(gf.gguf, name.c_str());
if (idx < 0) {
fprintf(stderr, "[GGUF] FATAL: tensor '%s' not found\n", name.c_str());
exit(1);
}
struct ggml_tensor * src = ggml_get_tensor(gf.meta, name.c_str());
int n_dims = ggml_n_dims(src);
int64_t ne[4] = {1, 1, 1, 1};
for (int i = 0; i < n_dims; i++) ne[i] = src->ne[i];
// If already F32, just load normally
if (src->type == GGML_TYPE_F32) {
return gf_load_tensor(wctx, gf, name);
}
// Bail early on unsupported types (before creating tensor in ctx)
if (src->type != GGML_TYPE_BF16 && src->type != GGML_TYPE_F16) {
fprintf(stderr, "[GGUF] WARNING: gf_load_tensor_f32 unsupported type %d for '%s', loading as-is\n",
src->type, name.c_str());
return gf_load_tensor(wctx, gf, name);
}
// Create F32 tensor
struct ggml_tensor * tensor = ggml_new_tensor(wctx->ctx, GGML_TYPE_F32, n_dims, ne);
ggml_set_name(tensor, name.c_str());
// Convert data into staging buffer
size_t n = ggml_nelements(src);
wctx->staging.emplace_back(n);
std::vector<float> & buf = wctx->staging.back();
size_t offset = gguf_get_tensor_offset(gf.gguf, idx);
const void * raw = gf.mapping + gf.data_offset + offset;
if (src->type == GGML_TYPE_BF16) {
const uint16_t * p = (const uint16_t *)raw;
for (size_t i = 0; i < n; i++)
buf[i] = ggml_bf16_to_fp32(*(const ggml_bf16_t *)&p[i]);
} else {
ggml_fp16_to_fp32_row((const ggml_fp16_t *)raw, buf.data(), (int)n);
}
wctx->pending.push_back({tensor, buf.data(), n * sizeof(float), 0});
return tensor;
}
// Get raw pointer to tensor data in the mmapped file.
// Useful for CPU-side operations (e.g. bf16 embed lookup for lyrics).
// Returns NULL if not found.
static const void * gf_get_data(const GGUFModel & gf, const char * name) {
int64_t idx = gguf_find_tensor(gf.gguf, name);
if (idx < 0) return NULL;
size_t offset = gguf_get_tensor_offset(gf.gguf, idx);
return gf.mapping + gf.data_offset + offset;
}
// Fuse Q, K, V projection weights into a single tensor [ne0, q_ne1 + k_ne1 + v_ne1].
// Works for any quantized type since quantization is per-row (along ne[0]).
// The fused tensor data is q rows || k rows || v rows (contiguous).
static struct ggml_tensor * gf_load_qkv_fused(
WeightCtx * wctx,
const GGUFModel & gf,
const std::string & q_name,
const std::string & k_name,
const std::string & v_name) {
struct ggml_tensor * q_src = ggml_get_tensor(gf.meta, q_name.c_str());
struct ggml_tensor * k_src = ggml_get_tensor(gf.meta, k_name.c_str());
struct ggml_tensor * v_src = ggml_get_tensor(gf.meta, v_name.c_str());
if (!q_src || !k_src || !v_src) {
fprintf(stderr, "[GGUF] FATAL: QKV tensor not found: %s / %s / %s\n",
q_name.c_str(), k_name.c_str(), v_name.c_str());
exit(1);
}
// All must share ne[0] (input dim) and type - otherwise can't fuse
GGML_ASSERT(q_src->ne[0] == k_src->ne[0] && k_src->ne[0] == v_src->ne[0]);
if (q_src->type != k_src->type || k_src->type != v_src->type) {
return NULL; // caller should fall back to separate loads
}
int64_t ne0 = q_src->ne[0];
int64_t fused_ne1 = q_src->ne[1] + k_src->ne[1] + v_src->ne[1];
int64_t ne[2] = { ne0, fused_ne1 };
struct ggml_tensor * fused = ggml_new_tensor(wctx->ctx, q_src->type, 2, ne);
size_t row_size = ggml_row_size(q_src->type, ne0);
size_t q_bytes = q_src->ne[1] * row_size;
size_t k_bytes = k_src->ne[1] * row_size;
size_t v_bytes = v_src->ne[1] * row_size;
auto get_data = [&](const std::string & name) -> const void * {
int64_t idx = gguf_find_tensor(gf.gguf, name.c_str());
size_t off = gguf_get_tensor_offset(gf.gguf, idx);
return gf.mapping + gf.data_offset + off;
};
wctx->pending.push_back({fused, get_data(q_name), q_bytes, 0});
wctx->pending.push_back({fused, get_data(k_name), k_bytes, q_bytes});
wctx->pending.push_back({fused, get_data(v_name), v_bytes, q_bytes + k_bytes});
return fused;
}
// Fuse two projection weights [ne0, a_ne1 + b_ne1] when types match.
// Returns NULL if types differ.
static struct ggml_tensor * gf_load_pair_fused(
WeightCtx * wctx,
const GGUFModel & gf,
const std::string & a_name,
const std::string & b_name) {
struct ggml_tensor * a_src = ggml_get_tensor(gf.meta, a_name.c_str());
struct ggml_tensor * b_src = ggml_get_tensor(gf.meta, b_name.c_str());
if (!a_src || !b_src) return NULL;
if (a_src->ne[0] != b_src->ne[0] || a_src->type != b_src->type) return NULL;
int64_t ne0 = a_src->ne[0];
int64_t ne[2] = { ne0, a_src->ne[1] + b_src->ne[1] };
struct ggml_tensor * fused = ggml_new_tensor(wctx->ctx, a_src->type, 2, ne);
size_t row_size = ggml_row_size(a_src->type, ne0);
size_t a_bytes = a_src->ne[1] * row_size;
size_t b_bytes = b_src->ne[1] * row_size;
auto get_data = [&](const std::string & name) -> const void * {
int64_t idx = gguf_find_tensor(gf.gguf, name.c_str());
size_t off = gguf_get_tensor_offset(gf.gguf, idx);
return gf.mapping + gf.data_offset + off;
};
wctx->pending.push_back({fused, get_data(a_name), a_bytes, 0});
wctx->pending.push_back({fused, get_data(b_name), b_bytes, a_bytes});
return fused;
}
// Read a uint32 KV value (returns 0 if not found)
static uint32_t gf_get_u32(const GGUFModel & gf, const char * key) {
int64_t idx = gguf_find_key(gf.gguf, key);
if (idx < 0) return 0;
return gguf_get_val_u32(gf.gguf, idx);
}
// Read a float32 KV value (returns 0 if not found)
static float gf_get_f32(const GGUFModel & gf, const char * key) {
int64_t idx = gguf_find_key(gf.gguf, key);
if (idx < 0) return 0.0f;
return gguf_get_val_f32(gf.gguf, idx);
}
// Read a string KV value (returns "" if not found)
static const char * gf_get_str(const GGUFModel & gf, const char * key) {
int64_t idx = gguf_find_key(gf.gguf, key);
if (idx < 0) return "";
return gguf_get_val_str(gf.gguf, idx);
}
// Read a bool KV value (returns false if not found)
static bool gf_get_bool(const GGUFModel & gf, const char * key) {
int64_t idx = gguf_find_key(gf.gguf, key);
if (idx < 0) return false;
return gguf_get_val_bool(gf.gguf, idx);
}

View file

@ -0,0 +1,379 @@
// quantize.cpp : GGUF requantizer for ACE-Step
// Reads BF16 GGUF, writes quantized GGUF with mixed-precision K-quant policy.
// Policy mirrors llama-quantize: important tensors (v_proj, down_proj) get
// bumped in S/M variants, embed_tokens always Q6_K, norms promoted to F32.
// Streaming write: one tensor at a time, low memory footprint for small configs.
//
// Usage: quantize <input.gguf> <output.gguf> <type>
// Types: Q2_K Q3_K_S Q3_K_M Q3_K_L Q4_K_S Q4_K_M Q5_K_S Q5_K_M Q6_K Q8_0
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <string>
#include <vector>
#ifdef _WIN32
# include <windows.h>
# define strcasecmp _stricmp
#else
# include <sys/mman.h>
# include <sys/stat.h>
# include <fcntl.h>
# include <unistd.h>
#endif
#include "ggml.h"
#include "gguf.h"
// Quant variant: base type + optional bump rules for important tensors
struct QuantVariant {
const char * name;
enum ggml_type base;
enum ggml_type bump; // type for "important" tensors (or COUNT = no bump)
enum ggml_type embed; // type for embed_tokens (or COUNT = same as base)
// bump_mode: 0=none, 1=first N layers, 2=first+last+every 3rd, 3=all important
int bump_mode;
int bump_n; // for mode 1: number of layers to bump
};
static const QuantVariant VARIANTS[] = {
// name base bump embed mode n
{"Q2_K", GGML_TYPE_Q2_K, GGML_TYPE_Q4_K, GGML_TYPE_Q6_K, 1, 4},
{"Q3_K_S", GGML_TYPE_Q3_K, GGML_TYPE_COUNT, GGML_TYPE_Q6_K, 0, 0},
{"Q3_K_M", GGML_TYPE_Q3_K, GGML_TYPE_Q5_K, GGML_TYPE_Q6_K, 2, 0},
{"Q3_K_L", GGML_TYPE_Q3_K, GGML_TYPE_Q5_K, GGML_TYPE_Q6_K, 3, 0},
{"Q4_K_S", GGML_TYPE_Q4_K, GGML_TYPE_Q5_K, GGML_TYPE_Q6_K, 1, 4},
{"Q4_K_M", GGML_TYPE_Q4_K, GGML_TYPE_Q6_K, GGML_TYPE_Q6_K, 2, 0},
{"Q5_K_S", GGML_TYPE_Q5_K, GGML_TYPE_COUNT, GGML_TYPE_Q6_K, 0, 0},
{"Q5_K_M", GGML_TYPE_Q5_K, GGML_TYPE_Q6_K, GGML_TYPE_Q6_K, 2, 0},
{"Q6_K", GGML_TYPE_Q6_K, GGML_TYPE_COUNT, GGML_TYPE_Q6_K, 0, 0},
{"Q8_0", GGML_TYPE_Q8_0, GGML_TYPE_COUNT, GGML_TYPE_Q8_0, 0, 0},
};
static const QuantVariant * find_variant(const char * s) {
for (const auto & v : VARIANTS) {
if (strcasecmp(s, v.name) == 0) return &v;
}
return nullptr;
}
// Extract layer index from HF tensor name: model.layers.N.xxx -> N, else -1
static int extract_layer(const char * name) {
const char * p = strstr(name, "layers.");
if (!p) return -1;
return atoi(p + 7);
}
// Important tensors for S/M: v_proj + down_proj
static bool is_important_sm(const char * name) {
return (strstr(name, "v_proj.weight") != nullptr) ||
(strstr(name, "down_proj.weight") != nullptr);
}
// Important tensors for L: v_proj + down_proj + o_proj
static bool is_important_l(const char * name) {
return is_important_sm(name) ||
(strstr(name, "o_proj.weight") != nullptr);
}
static bool is_embed(const char * name) {
return strstr(name, "embed_tokens.weight") != nullptr;
}
// Should this tensor be quantized at all?
static bool should_quantize(const char * name, int n_dims, const char * arch) {
if (strstr(arch, "vae")) return false;
if (n_dims < 2) return false;
if (strstr(arch, "text-enc") && strstr(name, "embed_tokens")) return false;
if (strstr(name, "silence_latent")) return false;
if (strstr(name, "scale_shift_table")) return false;
if (strstr(name, "null_condition_emb")) return false;
return true;
}
// Decide target type for a single tensor given the variant + layer info
static enum ggml_type pick_type(const char * name, int n_dims, const char * arch,
const QuantVariant & v, int n_layers) {
if (!should_quantize(name, n_dims, arch)) return GGML_TYPE_COUNT;
// embed_tokens in LM: use embed type
if (is_embed(name) && !strstr(arch, "text-enc")) {
return (v.embed != GGML_TYPE_COUNT) ? v.embed : v.base;
}
// Important tensor bump logic
bool important = (v.bump_mode == 3) ? is_important_l(name) : is_important_sm(name);
if (important && v.bump != GGML_TYPE_COUNT) {
int layer = extract_layer(name);
bool bumped = false;
switch (v.bump_mode) {
case 1: // first N layers only
bumped = (layer >= 0 && layer < v.bump_n);
break;
case 2: { // M variant: first few + last few + every 3rd
int ql = n_layers;
bumped = (layer >= 0) &&
(layer < ql / 9 || layer >= ql - ql / 7 || layer % 3 == 0);
break;
}
case 3: // L variant: all important tensors (v+down+o_proj)
bumped = true;
break;
}
if (bumped) return v.bump;
}
return v.base;
}
// Promote 1D tensors (norms/biases) to F32 for precision
static bool should_promote_f32(int n_dims) {
return n_dims < 2;
}
// Convert source data to F32
static bool to_f32(const void * src, float * dst, int64_t n, enum ggml_type type) {
switch (type) {
case GGML_TYPE_BF16:
ggml_bf16_to_fp32_row((const ggml_bf16_t *)src, dst, n);
return true;
case GGML_TYPE_F16:
ggml_fp16_to_fp32_row((const ggml_fp16_t *)src, dst, n);
return true;
case GGML_TYPE_F32:
memcpy(dst, src, (size_t)n * sizeof(float));
return true;
default:
return false;
}
}
int main(int argc, char ** argv) {
if (argc != 4) {
fprintf(stderr, "Usage: %s <input.gguf> <output.gguf> <type>\n", argv[0]);
fprintf(stderr, "Types:");
for (const auto & v : VARIANTS) fprintf(stderr, " %s", v.name);
fprintf(stderr, "\n");
return 1;
}
const char * inp_path = argv[1];
const char * out_path = argv[2];
const QuantVariant * variant = find_variant(argv[3]);
if (!variant) {
fprintf(stderr, "[Quantize] Unknown type: %s\n", argv[3]);
return 1;
}
fprintf(stderr, "[Quantize] %s -> %s (%s)\n", inp_path, out_path, variant->name);
// Mmap input file
#ifdef _WIN32
HANDLE fh = CreateFileA(inp_path, GENERIC_READ, FILE_SHARE_READ, NULL,
OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (fh == INVALID_HANDLE_VALUE) {
fprintf(stderr, "[Quantize] Failed to open %s\n", inp_path);
return 1;
}
HANDLE mh = CreateFileMappingA(fh, NULL, PAGE_READONLY, 0, 0, NULL);
if (!mh) {
fprintf(stderr, "[Quantize] CreateFileMapping failed %s\n", inp_path);
CloseHandle(fh);
return 1;
}
void * mapping = MapViewOfFile(mh, FILE_MAP_READ, 0, 0, 0);
if (!mapping) {
fprintf(stderr, "[Quantize] MapViewOfFile failed %s\n", inp_path);
CloseHandle(mh); CloseHandle(fh);
return 1;
}
#else
int fd = open(inp_path, O_RDONLY);
if (fd < 0) { perror("open"); return 1; }
struct stat st;
fstat(fd, &st);
size_t file_size = (size_t)st.st_size;
void * mapping = mmap(nullptr, file_size, PROT_READ, MAP_PRIVATE, fd, 0);
if (mapping == MAP_FAILED) { perror("mmap"); close(fd); return 1; }
#endif
// Parse input GGUF
struct gguf_init_params params = { /*no_alloc=*/ true, /*ctx=*/ nullptr };
struct ggml_context * meta = nullptr;
params.ctx = &meta;
struct gguf_context * inp = gguf_init_from_file(inp_path, params);
if (!inp) {
fprintf(stderr, "[Quantize] Failed to read %s\n", inp_path);
#ifdef _WIN32
UnmapViewOfFile(mapping); CloseHandle(mh); CloseHandle(fh);
#else
munmap(mapping, file_size); close(fd);
#endif
return 1;
}
const size_t data_off = gguf_get_data_offset(inp);
const int n_tensors = (int)gguf_get_n_tensors(inp);
// Read architecture
char arch[64] = "unknown";
{
int64_t idx = gguf_find_key(inp, "general.architecture");
if (idx >= 0) {
const char * s = gguf_get_val_str(inp, (int)idx);
snprintf(arch, sizeof(arch), "%s", s);
}
}
// Read block count for bump policy
int n_layers = 0;
{
char key[128];
snprintf(key, sizeof(key), "%s.block_count", arch);
int64_t idx = gguf_find_key(inp, key);
if (idx >= 0) n_layers = (int)gguf_get_val_u32(inp, (int)idx);
}
fprintf(stderr, "[Quantize] Arch=%s Layers=%d\n", arch, n_layers);
// Create output GGUF: copy KV metadata
struct gguf_context * out = gguf_init_empty();
gguf_set_kv(out, inp);
gguf_set_val_u32(out, "general.quantization_version", 2);
gguf_set_val_str(out, "general.file_type", variant->name);
// Plan: for each tensor, decide target type
struct TensorPlan { enum ggml_type target; bool quantize; bool promote; };
std::vector<TensorPlan> plans((size_t)n_tensors);
for (int i = 0; i < n_tensors; i++) {
const char * name = gguf_get_tensor_name(inp, i);
struct ggml_tensor * t = ggml_get_tensor(meta, name);
const int n_dims = ggml_n_dims(t);
gguf_add_tensor(out, t);
plans[(size_t)i] = {GGML_TYPE_COUNT, false, false};
enum ggml_type target = pick_type(name, n_dims, arch, *variant, n_layers);
// Promote 1D norms/biases BF16/F16 -> F32
if (target == GGML_TYPE_COUNT && should_promote_f32(n_dims) &&
(t->type == GGML_TYPE_BF16 || t->type == GGML_TYPE_F16)) {
gguf_set_tensor_type(out, name, GGML_TYPE_F32);
plans[(size_t)i] = {GGML_TYPE_F32, false, true};
continue;
}
if (target == GGML_TYPE_COUNT) continue;
bool can_convert = (t->type == GGML_TYPE_BF16 ||
t->type == GGML_TYPE_F16 ||
t->type == GGML_TYPE_F32);
bool aligned = (t->ne[0] % ggml_blck_size(target) == 0);
if (can_convert && aligned) {
gguf_set_tensor_type(out, name, target);
plans[(size_t)i] = {target, true, false};
}
}
// Write metadata only (header + tensor info, no data)
bool ok = gguf_write_to_file(out, out_path, true);
if (!ok) {
fprintf(stderr, "[Quantize] Failed to write metadata %s\n", out_path);
return 1;
}
// Stream tensor data one at a time (low memory)
FILE * fout = fopen(out_path, "ab");
if (!fout) {
fprintf(stderr, "[Quantize] Failed to open %s for append\n", out_path);
return 1;
}
const size_t alignment = gguf_get_alignment(out);
int n_quantized = 0, n_promoted = 0;
int64_t bytes_in = 0, bytes_out = 0;
size_t data_pos = 0;
for (int i = 0; i < n_tensors; i++) {
const char * name = gguf_get_tensor_name(inp, i);
struct ggml_tensor * t = ggml_get_tensor(meta, name);
const int64_t nel = ggml_nelements(t);
const size_t src_size = ggml_nbytes(t);
const size_t t_off = gguf_get_tensor_offset(inp, i);
const void * src = (const uint8_t *)mapping + data_off + t_off;
bytes_in += (int64_t)src_size;
// Pad to alignment boundary
size_t pad = (alignment - (data_pos % alignment)) % alignment;
if (pad > 0) {
uint8_t zeros[64] = {};
fwrite(zeros, 1, pad, fout);
data_pos += pad;
}
const TensorPlan & plan = plans[(size_t)i];
if (plan.promote) {
// BF16/F16 -> F32
std::vector<float> f32((size_t)nel);
to_f32(src, f32.data(), nel, t->type);
size_t out_size = (size_t)nel * sizeof(float);
fwrite(f32.data(), 1, out_size, fout);
data_pos += out_size;
bytes_out += (int64_t)out_size;
n_promoted++;
} else if (plan.quantize) {
// Quantize: src -> f32 -> target
std::vector<float> f32((size_t)nel);
to_f32(src, f32.data(), nel, t->type);
const int64_t n_per_row = t->ne[0];
const int64_t nrows = nel / n_per_row;
const size_t qsize = ggml_row_size(plan.target, n_per_row) * (size_t)nrows;
std::vector<uint8_t> qbuf(qsize);
ggml_quantize_chunk(plan.target, f32.data(), qbuf.data(),
0, nrows, n_per_row, nullptr);
fwrite(qbuf.data(), 1, qsize, fout);
data_pos += qsize;
bytes_out += (int64_t)qsize;
n_quantized++;
} else {
// Keep as-is
fwrite(src, 1, src_size, fout);
data_pos += src_size;
bytes_out += (int64_t)src_size;
}
}
fclose(fout);
fprintf(stderr, "[Quantize] Quantized %d/%d tensors, promoted %d to F32\n",
n_quantized, n_tensors, n_promoted);
fprintf(stderr, "[Quantize] %.1f GB -> %.1f GB (%.1fx)\n",
(double)bytes_in / 1e9, (double)bytes_out / 1e9,
bytes_out > 0 ? (double)bytes_in / (double)bytes_out : 0.0);
fprintf(stderr, "[Quantize] Wrote %s\n", out_path);
gguf_free(out);
gguf_free(inp);
ggml_free(meta);
#ifdef _WIN32
UnmapViewOfFile(mapping);
CloseHandle(mh);
CloseHandle(fh);
#else
munmap(mapping, file_size);
close(fd);
#endif
return 0;
}

View file

@ -0,0 +1,728 @@
// qwen3-lm.h : Qwen3 causal LM with KV cache (GGML)
// Autoregressive text + audio code generation for ACE-Step
// Loads from GGUF, supports prefill + decode, tied lm_head
#pragma once
#include "qwen3.h" // Qwen3Layer, Qwen3Config, layer build helpers
#include "bpe.h"
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <string>
#include <vector>
// LM config (superset of encoder config)
struct Qwen3LMConfig {
int vocab_size;
int hidden_size;
int intermediate_size;
int n_heads;
int n_kv_heads;
int head_dim;
int n_layers;
float rope_theta;
float rms_norm_eps;
bool tie_embeddings;
int max_seq_len; // KV cache capacity
};
// KV cache set (one per CFG path: conditional + unconditional)
#define QW3LM_MAX_KV_SETS 32 // batch N * 2 (cond + uncond CFG)
#define QW3LM_MAX_LAYERS 64
struct Qwen3LM {
Qwen3LMConfig cfg;
// Weights (on backend)
struct ggml_tensor * embed_tokens; // [H, V] on GPU (used by mul_mat lm_head)
struct ggml_tensor * final_norm; // [H]
// lm_head = embed_tokens when tie_embeddings
Qwen3Layer layers[QW3LM_MAX_LAYERS];
WeightCtx wctx;
ggml_backend_t backend;
ggml_backend_t cpu_backend;
ggml_backend_sched_t sched;
// CPU-side embed lookup via mmap (avoids ggml_get_rows which lacks
// CUDA K-quant support, preventing costly cross-backend tensor copies)
GGUFModel gf_mmap;
const void * embed_mmap_data;
enum ggml_type embed_type;
// KV cache: per-set, per-layer [D, max_seq, Nkv] f16
struct ggml_context * kv_ctx;
ggml_backend_buffer_t kv_buf;
struct ggml_tensor * kv_k[QW3LM_MAX_KV_SETS][QW3LM_MAX_LAYERS];
struct ggml_tensor * kv_v[QW3LM_MAX_KV_SETS][QW3LM_MAX_LAYERS];
int kv_pos[QW3LM_MAX_KV_SETS];
int n_kv_sets;
};
// Parse config.json integers, floats, bools
static int qw3lm_json_int(const char * json, const char * key, int fb) {
char needle[128];
snprintf(needle, sizeof(needle), "\"%s\"", key);
const char * p = strstr(json, needle);
if (!p) return fb;
p = strchr(p + strlen(needle), ':');
if (!p) return fb;
p++;
while (*p == ' ' || *p == '\t') p++;
return atoi(p);
}
static float qw3lm_json_float(const char * json, const char * key, float fb) {
char needle[128];
snprintf(needle, sizeof(needle), "\"%s\"", key);
const char * p = strstr(json, needle);
if (!p) return fb;
p = strchr(p + strlen(needle), ':');
if (!p) return fb;
p++;
while (*p == ' ' || *p == '\t') p++;
return (float)atof(p);
}
static bool qw3lm_json_bool(const char * json, const char * key, bool fb) {
char needle[128];
snprintf(needle, sizeof(needle), "\"%s\"", key);
const char * p = strstr(json, needle);
if (!p) return fb;
p = strchr(p + strlen(needle), ':');
if (!p) return fb;
p++;
while (*p == ' ' || *p == '\t') p++;
return (strncmp(p, "true", 4) == 0);
}
// Load config from GGUF KV metadata (acestep.config_json)
static Qwen3LMConfig qw3lm_load_config(const GGUFModel & gf) {
// 0.6B defaults
Qwen3LMConfig c = {
/*vocab_size*/ 217204,
/*hidden_size*/ 1024,
/*intermediate_size*/ 3072,
/*n_heads*/ 16,
/*n_kv_heads*/ 8,
/*head_dim*/ 128,
/*n_layers*/ 28,
/*rope_theta*/ 1000000.0f,
/*rms_norm_eps*/ 1e-6f,
/*tie_embeddings*/ true,
/*max_seq_len*/ 8192,
};
const char * j = gf_get_str(gf, "acestep.config_json");
if (!j || !j[0]) {
fprintf(stderr, "[LM-Config] No acestep.config_json, using 0.6B defaults\n");
return c;
}
c.vocab_size = qw3lm_json_int(j, "vocab_size", c.vocab_size);
c.hidden_size = qw3lm_json_int(j, "hidden_size", c.hidden_size);
c.intermediate_size = qw3lm_json_int(j, "intermediate_size", c.intermediate_size);
c.n_heads = qw3lm_json_int(j, "num_attention_heads", c.n_heads);
c.n_kv_heads = qw3lm_json_int(j, "num_key_value_heads", c.n_kv_heads);
c.head_dim = qw3lm_json_int(j, "head_dim", c.head_dim);
c.n_layers = qw3lm_json_int(j, "num_hidden_layers", c.n_layers);
c.rope_theta = qw3lm_json_float(j, "rope_theta", c.rope_theta);
c.rms_norm_eps = qw3lm_json_float(j, "rms_norm_eps", c.rms_norm_eps);
c.tie_embeddings = qw3lm_json_bool(j, "tie_word_embeddings", c.tie_embeddings);
fprintf(stderr, "[LM-Config] %dL, H=%d, V=%d, Nh=%d, Nkv=%d, D=%d, tied=%d\n",
c.n_layers, c.hidden_size, c.vocab_size, c.n_heads, c.n_kv_heads,
c.head_dim, c.tie_embeddings);
return c;
}
// Init backend (same pattern as qwen3.h)
static void qw3lm_init_backend(Qwen3LM * m) {
BackendPair bp = backend_init("LM");
m->backend = bp.backend;
m->cpu_backend = bp.cpu_backend;
m->sched = backend_sched_new(bp, 8192);
}
// Allocate KV cache
static void qw3lm_alloc_kv_cache(Qwen3LM * m, int n_sets) {
const Qwen3LMConfig & c = m->cfg;
int D = c.head_dim;
int Nkv = c.n_kv_heads;
int L = c.n_layers;
int S = c.max_seq_len;
m->n_kv_sets = n_sets;
// Each KV tensor: [D, max_seq, Nkv] f16
int n_tensors = n_sets * L * 2;
size_t ctx_size = (size_t)n_tensors * ggml_tensor_overhead() + 1024;
struct ggml_init_params gp = { ctx_size, NULL, true };
m->kv_ctx = ggml_init(gp);
for (int s = 0; s < n_sets; s++) {
for (int l = 0; l < L; l++) {
m->kv_k[s][l] = ggml_new_tensor_3d(m->kv_ctx, GGML_TYPE_F16, D, S, Nkv);
m->kv_v[s][l] = ggml_new_tensor_3d(m->kv_ctx, GGML_TYPE_F16, D, S, Nkv);
char name[64];
snprintf(name, sizeof(name), "kv_k_%d_%d", s, l);
ggml_set_name(m->kv_k[s][l], name);
snprintf(name, sizeof(name), "kv_v_%d_%d", s, l);
ggml_set_name(m->kv_v[s][l], name);
}
m->kv_pos[s] = 0;
}
m->kv_buf = ggml_backend_alloc_ctx_tensors(m->kv_ctx, m->backend);
if (!m->kv_buf) {
fprintf(stderr, "[LM-KV] FATAL: failed to allocate KV cache\n");
exit(1);
}
size_t kv_bytes = (size_t)n_sets * L * 2 * D * S * Nkv * ggml_type_size(GGML_TYPE_F16);
fprintf(stderr, "[LM-KV] Allocated %d sets x %d layers, %.1f MB\n",
n_sets, L, (float)kv_bytes / (1024 * 1024));
}
// Clear KV cache for a given set
static void qw3lm_reset_kv(Qwen3LM * m, int kv_set) {
m->kv_pos[kv_set] = 0;
// No need to zero memory: kv_pos tracks valid range
}
// Copy KV cache from one set to another (for batched prefill sharing)
static void qw3lm_copy_kv(Qwen3LM * m, int src, int dst) {
for (int l = 0; l < m->cfg.n_layers; l++) {
ggml_backend_tensor_copy(m->kv_k[src][l], m->kv_k[dst][l]);
ggml_backend_tensor_copy(m->kv_v[src][l], m->kv_v[dst][l]);
}
m->kv_pos[dst] = m->kv_pos[src];
}
// Load model weights from GGUF
static bool qw3lm_load(Qwen3LM * m, const char * gguf_path, int max_seq_len, int n_kv_sets) {
*m = {};
qw3lm_init_backend(m);
GGUFModel gf;
if (!gf_load(&gf, gguf_path)) {
fprintf(stderr, "[LM-Load] FATAL: cannot load %s\n", gguf_path);
return false;
}
m->cfg = qw3lm_load_config(gf);
if (max_seq_len > 0) m->cfg.max_seq_len = max_seq_len;
const Qwen3LMConfig & c = m->cfg;
if (c.n_layers > QW3LM_MAX_LAYERS) {
fprintf(stderr, "[LM-Load] FATAL: %d layers > max %d\n", c.n_layers, QW3LM_MAX_LAYERS);
gf_close(&gf);
return false;
}
// embed(1) + layers * 11 + final_norm(1) = 2 + n_layers * 11
int n_tensors = 2 + c.n_layers * 11;
wctx_init(&m->wctx, n_tensors);
m->embed_tokens = gf_load_tensor(&m->wctx, gf, "model.embed_tokens.weight");
m->final_norm = gf_load_tensor_f32(&m->wctx, gf, "model.norm.weight");
for (int i = 0; i < c.n_layers; i++) {
char prefix[64];
snprintf(prefix, sizeof(prefix), "model.layers.%d", i);
qwen3_load_layer(&m->wctx, gf, &m->layers[i], prefix);
}
wctx_alloc(&m->wctx, m->backend);
// Keep mmap alive for CPU embed dequant lookup
m->embed_mmap_data = gf_get_data(gf, "model.embed_tokens.weight");
m->embed_type = m->embed_tokens->type;
if (!m->embed_mmap_data) {
fprintf(stderr, "[LM-Load] FATAL: embed_tokens not found in mmap\n");
gf_close(&gf);
return false;
}
m->gf_mmap = gf; // transfer ownership (no gf_close here)
fprintf(stderr, "[LM-Load] CPU embed lookup: type=%s, row=%zu bytes\n",
ggml_type_name(m->embed_type),
ggml_row_size(m->embed_type, c.hidden_size));
// KV cache
qw3lm_alloc_kv_cache(m, n_kv_sets > 0 ? n_kv_sets : 1);
return true;
}
// Build self-attention with KV cache write + read
// x: [H, n_tokens], positions: [n_tokens], mask: [kv_len, n_tokens] or NULL
static struct ggml_tensor * qw3lm_build_attn(
struct ggml_context * ctx,
struct ggml_cgraph * gf,
const Qwen3LMConfig & c,
Qwen3Layer * ly,
struct ggml_tensor * x,
struct ggml_tensor * positions,
struct ggml_tensor * mask,
struct ggml_tensor * cache_k, // [D, max_seq, Nkv] f16
struct ggml_tensor * cache_v, // [D, max_seq, Nkv] f16
int kv_pos,
int kv_len,
int n_tokens) {
int D = c.head_dim;
int Nh = c.n_heads;
int Nkv = c.n_kv_heads;
int S = n_tokens;
// QKV projections
struct ggml_tensor * q = qwen3_linear(ctx, ly->q_proj, x); // [Nh*D, S]
struct ggml_tensor * k = qwen3_linear(ctx, ly->k_proj, x); // [Nkv*D, S]
struct ggml_tensor * v = qwen3_linear(ctx, ly->v_proj, x); // [Nkv*D, S]
// Reshape to heads: [X*D, S] -> [D, X, S]
q = ggml_reshape_3d(ctx, q, D, Nh, S);
k = ggml_reshape_3d(ctx, k, D, Nkv, S);
v = ggml_reshape_3d(ctx, v, D, Nkv, S);
// QK-Norm
q = ggml_rms_norm(ctx, q, c.rms_norm_eps);
q = ggml_mul(ctx, q, qwen3_f32(ctx, ly->q_norm));
k = ggml_rms_norm(ctx, k, c.rms_norm_eps);
k = ggml_mul(ctx, k, qwen3_f32(ctx, ly->k_norm));
// RoPE (NEOX mode=2)
q = ggml_rope_ext(ctx, q, positions, NULL,
D, 2, 0, c.rope_theta, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f);
k = ggml_rope_ext(ctx, k, positions, NULL,
D, 2, 0, c.rope_theta, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f);
// Permute for flash_attn: [D, X, S] -> [D, S, X]
q = ggml_permute(ctx, q, 0, 2, 1, 3); // [D, S, Nh]
k = ggml_permute(ctx, k, 0, 2, 1, 3); // [D, S, Nkv]
v = ggml_permute(ctx, v, 0, 2, 1, 3); // [D, S, Nkv]
// Make contiguous for cpy to f16 cache
k = ggml_cont(ctx, k);
v = ggml_cont(ctx, v);
// Write K,V to cache at kv_pos
// Cache layout: [D, max_seq, Nkv] f16
size_t nb1 = (size_t)D * ggml_type_size(GGML_TYPE_F16);
size_t nb2 = (size_t)D * c.max_seq_len * ggml_type_size(GGML_TYPE_F16);
size_t off = (size_t)kv_pos * nb1;
struct ggml_tensor * k_dst = ggml_view_3d(ctx, cache_k, D, S, Nkv, nb1, nb2, off);
struct ggml_tensor * v_dst = ggml_view_3d(ctx, cache_v, D, S, Nkv, nb1, nb2, off);
ggml_build_forward_expand(gf, ggml_cpy(ctx, k, k_dst));
ggml_build_forward_expand(gf, ggml_cpy(ctx, v, v_dst));
// Read full KV from cache [0..kv_len]
struct ggml_tensor * k_full = ggml_view_3d(ctx, cache_k, D, kv_len, Nkv, nb1, nb2, 0);
struct ggml_tensor * v_full = ggml_view_3d(ctx, cache_v, D, kv_len, Nkv, nb1, nb2, 0);
// Flash attention
float scale = 1.0f / sqrtf((float)D);
struct ggml_tensor * attn = ggml_flash_attn_ext(ctx, q, k_full, v_full, mask, scale, 0.0f, 0.0f);
ggml_flash_attn_ext_set_prec(attn, GGML_PREC_F32); // F32 accumulation
// Reshape: [D, Nh, S] -> [Nh*D, S]
attn = ggml_reshape_2d(ctx, attn, Nh * D, S);
// O projection
return qwen3_linear(ctx, ly->o_proj, attn);
}
// Forward pass: token_ids[n_tokens] -> logits[vocab_size] (last token only)
// kv_set: which KV cache set to use (0=conditional, 1=unconditional for CFG)
static void qw3lm_forward(Qwen3LM * m, const int * token_ids, int n_tokens,
int kv_set, float * logits) {
const Qwen3LMConfig & c = m->cfg;
int H = c.hidden_size;
int kv_pos = m->kv_pos[kv_set];
int kv_len = kv_pos + n_tokens;
if (kv_len > c.max_seq_len) {
fprintf(stderr, "[LM-Forward] FATAL: kv_len %d > max_seq %d\n", kv_len, c.max_seq_len);
return;
}
// Graph context
size_t ctx_size = (size_t)16384 * ggml_tensor_overhead() + ggml_graph_overhead();
struct ggml_init_params gp = { ctx_size, NULL, true };
struct ggml_context * ctx = ggml_init(gp);
struct ggml_cgraph * gf = ggml_new_graph_custom(ctx, 16384, false);
// Inputs
struct ggml_tensor * positions = ggml_new_tensor_1d(ctx, GGML_TYPE_I32, n_tokens);
ggml_set_name(positions, "positions");
ggml_set_input(positions);
// Causal mask: needed for prefill (n_tokens > 1), not for decode (n_tokens == 1)
struct ggml_tensor * mask = NULL;
if (n_tokens > 1) {
mask = ggml_new_tensor_2d(ctx, GGML_TYPE_F16, kv_len, n_tokens);
ggml_set_name(mask, "causal_mask");
ggml_set_input(mask);
}
// Embedding: CPU dequant from mmap, fed as F32 input.
// This keeps embed_tokens out of get_rows (no CUDA K-quant support)
// and only in mul_mat (lm_head) which has full K-quant CUDA support.
struct ggml_tensor * embed_out = ggml_new_tensor_2d(ctx, GGML_TYPE_F32, H, n_tokens);
ggml_set_name(embed_out, "embed_out");
ggml_set_input(embed_out);
struct ggml_tensor * hidden = embed_out;
// Transformer layers
for (int l = 0; l < c.n_layers; l++) {
Qwen3Layer * ly = &m->layers[l];
// Pre-attention norm
struct ggml_tensor * norm = qwen3_rms_norm(ctx, hidden, ly->input_layernorm, c.rms_norm_eps);
// Self-attention with KV cache
struct ggml_tensor * attn = qw3lm_build_attn(
ctx, gf, c, ly, norm, positions, mask,
m->kv_k[kv_set][l], m->kv_v[kv_set][l],
kv_pos, kv_len, n_tokens);
// Residual
hidden = ggml_add(ctx, hidden, attn);
// Post-attention norm + MLP
norm = qwen3_rms_norm(ctx, hidden, ly->post_attn_layernorm, c.rms_norm_eps);
struct ggml_tensor * mlp = qwen3_build_mlp(ctx, ly, norm, n_tokens);
hidden = ggml_add(ctx, hidden, mlp);
}
// Final norm
hidden = qwen3_rms_norm(ctx, hidden, m->final_norm, c.rms_norm_eps);
// Extract last token hidden state: [H, n_tokens] -> [H, 1]
if (n_tokens > 1) {
hidden = ggml_view_1d(ctx, hidden, H,
(int64_t)(n_tokens - 1) * H * sizeof(float));
}
// LM head: logits = embed_tokens^T @ hidden -> [V, 1]
struct ggml_tensor * lgt = ggml_mul_mat(ctx, m->embed_tokens, hidden);
ggml_set_name(lgt, "logits");
ggml_set_output(lgt);
ggml_build_forward_expand(gf, lgt);
// Schedule + allocate
ggml_backend_sched_alloc_graph(m->sched, gf);
// CPU-side embedding dequantization from mmap
{
const int64_t row_size = (int64_t)ggml_row_size(m->embed_type, H);
const ggml_to_float_t to_float = ggml_get_type_traits(m->embed_type)->to_float;
std::vector<float> embed_buf((size_t)H * n_tokens);
for (int i = 0; i < n_tokens; i++) {
const void * row = (const char *)m->embed_mmap_data + (int64_t)token_ids[i] * row_size;
to_float(row, embed_buf.data() + (int64_t)i * H, H);
}
ggml_backend_tensor_set(embed_out, embed_buf.data(), 0,
(size_t)H * n_tokens * sizeof(float));
}
{
std::vector<int> pos_data(n_tokens);
for (int i = 0; i < n_tokens; i++) pos_data[i] = kv_pos + i;
ggml_backend_tensor_set(positions, pos_data.data(), 0, n_tokens * sizeof(int));
}
if (mask) {
// Causal mask: [kv_len, n_tokens]
// Row i (query at position kv_pos+i) can attend to columns [0..kv_pos+i]
std::vector<uint16_t> mask_data((size_t)kv_len * n_tokens);
for (int i = 0; i < n_tokens; i++) {
int query_abs_pos = kv_pos + i;
for (int j = 0; j < kv_len; j++) {
float v = (j <= query_abs_pos) ? 0.0f : -INFINITY;
mask_data[(size_t)i * kv_len + j] = ggml_fp32_to_fp16(v);
}
}
ggml_backend_tensor_set(mask, mask_data.data(), 0,
(size_t)kv_len * n_tokens * sizeof(uint16_t));
}
// Compute
ggml_backend_sched_graph_compute(m->sched, gf);
// Read logits [V]
ggml_backend_tensor_get(lgt, logits, 0, c.vocab_size * sizeof(float));
// Advance KV position
m->kv_pos[kv_set] += n_tokens;
ggml_backend_sched_reset(m->sched);
ggml_free(ctx);
}
// Batched decode forward: N tokens (1 per sequence), batched weight matmuls.
// kv_pos per element from m->kv_pos[kv_sets[i]], supports different prompt lengths.
// kv_sets[N]: which KV set each token uses.
// logits: [N * V] output, N logit vectors concatenated.
static void qw3lm_forward_batch(Qwen3LM * m, const int * token_ids,
const int * kv_sets, int N, float * logits) {
const Qwen3LMConfig & c = m->cfg;
int H = c.hidden_size;
int D = c.head_dim;
int Nh = c.n_heads;
int Nkv = c.n_kv_heads;
// Per-element kv_pos (supports different prompt lengths)
int max_kv_len = 0;
for (int i = 0; i < N; i++) {
int kl = m->kv_pos[kv_sets[i]] + 1;
if (kl > max_kv_len) max_kv_len = kl;
if (kl > c.max_seq_len) {
fprintf(stderr, "[LM-Batch] FATAL: kv_len %d > max_seq %d (set %d)\n",
kl, c.max_seq_len, kv_sets[i]);
exit(1);
}
}
// Exact tensor count for context allocation
// qwen3_f32(t) creates 0 tensors if t is F32, 1 (ggml_cast) if not.
// Count conditionally based on actual weight types.
//
// GLOBAL (2):
// embed_out (new_tensor_2d) 1
// positions (new_tensor_1d) 1
//
// PER LAYER fixed (17):
// qwen3_rms_norm(input_layernorm):
// ggml_rms_norm + ggml_mul 2
// q_proj, k_proj, v_proj (qwen3_linear = mul_mat) 3
// reshape_3d (q, k, v) 3
// ggml_rms_norm(q) + ggml_mul(q, q_norm) 2
// ggml_rms_norm(k) + ggml_mul(k, k_norm) 2
// ggml_rope_ext (q, k) 2
// ggml_cont (q, k, v) 3
//
// PER LAYER * N (16 each):
// view_3d (qi, ki, vi) 3
// permute (qi, ki, vi) 3
// cont (ki, vi) 2
// view_3d (k_dst, v_dst) 2
// cpy (ki, vi) 2
// view_3d (k_full, v_full) 2
// flash_attn_ext 1
// reshape_2d 1
//
// CONCATS: N-1 (first element reuses reshape_2d)
//
// PER LAYER post (9):
// qwen3_linear(o_proj) 1
// ggml_add (residual) 1
// qwen3_rms_norm(post_attn_layernorm):
// ggml_rms_norm + ggml_mul 2
// qwen3_build_mlp:
// gate_proj, up_proj (mul_mat) 2
// ggml_swiglu_split 1
// down_proj (mul_mat) 1
// ggml_add (residual) 1
//
// PER LAYER conditional casts (0 to 4):
// qwen3_f32(input_layernorm) 0 or 1
// qwen3_f32(q_norm) 0 or 1
// qwen3_f32(k_norm) 0 or 1
// qwen3_f32(post_attn_norm) 0 or 1
//
// POST LAYERS (3):
// qwen3_rms_norm(final_norm): ggml_rms_norm + ggml_mul 2
// ggml_mul_mat (lm_head) 1
//
// POST conditional cast (0 or 1):
// qwen3_f32(final_norm) 0 or 1
//
// TOTAL = 5 + n_layers * (25 + 17*N + casts_per_layer) + 3 + cast_final
// = 8 + n_layers * (25 + 17*N + casts_per_layer) + cast_final
int casts_per_layer = 0;
if (m->layers[0].input_layernorm->type != GGML_TYPE_F32) casts_per_layer++;
if (m->layers[0].q_norm->type != GGML_TYPE_F32) casts_per_layer++;
if (m->layers[0].k_norm->type != GGML_TYPE_F32) casts_per_layer++;
if (m->layers[0].post_attn_layernorm->type != GGML_TYPE_F32) casts_per_layer++;
int cast_final = (m->final_norm->type != GGML_TYPE_F32) ? 1 : 0;
size_t n_tensors = 8
+ (size_t)c.n_layers * (25 + 17 * N + casts_per_layer)
+ cast_final;
size_t est = n_tensors * ggml_tensor_overhead()
+ ggml_graph_overhead_custom(16384, false);
struct ggml_init_params gp = { est, NULL, true };
struct ggml_context * ctx = ggml_init(gp);
struct ggml_cgraph * gf = ggml_new_graph_custom(ctx, 16384, false);
// Embedding: [H, N]
struct ggml_tensor * embed_out = ggml_new_tensor_2d(ctx, GGML_TYPE_F32, H, N);
ggml_set_name(embed_out, "embed_out");
ggml_set_input(embed_out);
// Positions: [N], per-element kv_pos
struct ggml_tensor * positions = ggml_new_tensor_1d(ctx, GGML_TYPE_I32, N);
ggml_set_name(positions, "positions");
ggml_set_input(positions);
struct ggml_tensor * hidden = embed_out;
for (int l = 0; l < c.n_layers; l++) {
Qwen3Layer * ly = &m->layers[l];
// Pre-attention norm [H, N]
struct ggml_tensor * norm = qwen3_rms_norm(ctx, hidden, ly->input_layernorm, c.rms_norm_eps);
// Batched QKV projections (weights read once for N tokens)
struct ggml_tensor * q = qwen3_linear(ctx, ly->q_proj, norm); // [Nh*D, N]
struct ggml_tensor * k = qwen3_linear(ctx, ly->k_proj, norm); // [Nkv*D, N]
struct ggml_tensor * v = qwen3_linear(ctx, ly->v_proj, norm); // [Nkv*D, N]
// Reshape to heads: [D, Heads, N]
q = ggml_reshape_3d(ctx, q, D, Nh, N);
k = ggml_reshape_3d(ctx, k, D, Nkv, N);
v = ggml_reshape_3d(ctx, v, D, Nkv, N);
// QK-Norm (rms_norm on dim0=D, per head per seq)
q = ggml_rms_norm(ctx, q, c.rms_norm_eps);
q = ggml_mul(ctx, q, qwen3_f32(ctx, ly->q_norm));
k = ggml_rms_norm(ctx, k, c.rms_norm_eps);
k = ggml_mul(ctx, k, qwen3_f32(ctx, ly->k_norm));
// RoPE: positions [N] maps to dim 2 of [D, Heads, N]
q = ggml_rope_ext(ctx, q, positions, NULL, D, 2, 0,
c.rope_theta, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f);
k = ggml_rope_ext(ctx, k, positions, NULL, D, 2, 0,
c.rope_theta, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f);
// Contiguous for clean slicing
q = ggml_cont(ctx, q);
k = ggml_cont(ctx, k);
v = ggml_cont(ctx, v);
// Per-sequence attention with individual KV caches
float scale = 1.0f / sqrtf((float)D);
size_t nb1_c = (size_t)D * ggml_type_size(GGML_TYPE_F16);
size_t nb2_c = (size_t)D * c.max_seq_len * ggml_type_size(GGML_TYPE_F16);
struct ggml_tensor * attn_cat = NULL;
for (int i = 0; i < N; i++) {
int set = kv_sets[i];
int elem_kv_pos = m->kv_pos[set];
int elem_kv_len = elem_kv_pos + 1;
size_t off_c = (size_t)elem_kv_pos * nb1_c;
// Slice [D, Heads, 1] from [D, Heads, N]
struct ggml_tensor * qi = ggml_view_3d(ctx, q, D, Nh, 1,
q->nb[1], q->nb[2], (size_t)i * q->nb[2]);
struct ggml_tensor * ki = ggml_view_3d(ctx, k, D, Nkv, 1,
k->nb[1], k->nb[2], (size_t)i * k->nb[2]);
struct ggml_tensor * vi = ggml_view_3d(ctx, v, D, Nkv, 1,
v->nb[1], v->nb[2], (size_t)i * v->nb[2]);
// Permute [D, Heads, 1] to [D, 1, Heads]
qi = ggml_permute(ctx, qi, 0, 2, 1, 3);
ki = ggml_permute(ctx, ki, 0, 2, 1, 3);
vi = ggml_permute(ctx, vi, 0, 2, 1, 3);
ki = ggml_cont(ctx, ki);
vi = ggml_cont(ctx, vi);
// Write K,V to cache at kv_pos
struct ggml_tensor * k_dst = ggml_view_3d(ctx, m->kv_k[set][l],
D, 1, Nkv, nb1_c, nb2_c, off_c);
struct ggml_tensor * v_dst = ggml_view_3d(ctx, m->kv_v[set][l],
D, 1, Nkv, nb1_c, nb2_c, off_c);
ggml_build_forward_expand(gf, ggml_cpy(ctx, ki, k_dst));
ggml_build_forward_expand(gf, ggml_cpy(ctx, vi, v_dst));
// Read full KV [0..elem_kv_len]
struct ggml_tensor * k_full = ggml_view_3d(ctx, m->kv_k[set][l],
D, elem_kv_len, Nkv, nb1_c, nb2_c, 0);
struct ggml_tensor * v_full = ggml_view_3d(ctx, m->kv_v[set][l],
D, elem_kv_len, Nkv, nb1_c, nb2_c, 0);
// Flash attention
struct ggml_tensor * attn_i = ggml_flash_attn_ext(ctx, qi, k_full, v_full,
NULL, scale, 0.0f, 0.0f);
ggml_flash_attn_ext_set_prec(attn_i, GGML_PREC_F32); // F32 accumulation
attn_i = ggml_reshape_2d(ctx, attn_i, Nh * D, 1);
if (i == 0) attn_cat = attn_i;
else attn_cat = ggml_concat(ctx, attn_cat, attn_i, 1);
}
// attn_cat: [Nh*D, N]
// Batched O proj
struct ggml_tensor * attn_out = qwen3_linear(ctx, ly->o_proj, attn_cat);
hidden = ggml_add(ctx, hidden, attn_out);
// Batched FFN
norm = qwen3_rms_norm(ctx, hidden, ly->post_attn_layernorm, c.rms_norm_eps);
struct ggml_tensor * mlp = qwen3_build_mlp(ctx, ly, norm, N);
hidden = ggml_add(ctx, hidden, mlp);
}
// Final norm + LM head: [V, N]
hidden = qwen3_rms_norm(ctx, hidden, m->final_norm, c.rms_norm_eps);
struct ggml_tensor * lgt = ggml_mul_mat(ctx, m->embed_tokens, hidden);
ggml_set_name(lgt, "logits");
ggml_set_output(lgt);
ggml_build_forward_expand(gf, lgt);
// Schedule + allocate
ggml_backend_sched_alloc_graph(m->sched, gf);
// CPU-side embedding dequant
{
const int64_t row_size = (int64_t)ggml_row_size(m->embed_type, H);
const ggml_to_float_t to_float = ggml_get_type_traits(m->embed_type)->to_float;
std::vector<float> embed_buf((size_t)H * N);
for (int i = 0; i < N; i++) {
const void * row = (const char *)m->embed_mmap_data + (int64_t)token_ids[i] * row_size;
to_float(row, embed_buf.data() + (int64_t)i * H, H);
}
ggml_backend_tensor_set(embed_out, embed_buf.data(), 0, (size_t)H * N * sizeof(float));
}
// Positions: per-element kv_pos
{
std::vector<int> pos_data(N);
for (int i = 0; i < N; i++)
pos_data[i] = m->kv_pos[kv_sets[i]];
ggml_backend_tensor_set(positions, pos_data.data(), 0, N * sizeof(int));
}
// Compute
ggml_backend_sched_graph_compute(m->sched, gf);
// Read logits [V, N]
ggml_backend_tensor_get(lgt, logits, 0, (size_t)c.vocab_size * N * sizeof(float));
// Advance all KV positions
for (int i = 0; i < N; i++)
m->kv_pos[kv_sets[i]]++;
ggml_backend_sched_reset(m->sched);
ggml_free(ctx);
}
// Free all resources
static void qw3lm_free(Qwen3LM * m) {
if (m->sched) ggml_backend_sched_free(m->sched);
if (m->kv_buf) ggml_backend_buffer_free(m->kv_buf);
if (m->kv_ctx) ggml_free(m->kv_ctx);
if (m->backend && m->backend != m->cpu_backend) ggml_backend_free(m->backend);
if (m->cpu_backend) ggml_backend_free(m->cpu_backend);
wctx_free(&m->wctx);
gf_close(&m->gf_mmap);
*m = {};
}

384
otherarch/acestep/qwen3.h Normal file
View file

@ -0,0 +1,384 @@
// qwen3.h: Qwen3 transformer encoder via ggml
//
// Generic Qwen3 backbone used by:
// Text encoder (Qwen3-Embedding-0.6B): 28L, H=1024, causal, vocab lookup
// Lyric encoder (ACEStep cond): 8L, H=2048, bidir, linear(1024->2048)
// Timbre encoder (ACEStep cond): 4L, H=2048, bidir, linear(64->2048)
//
// Architecture per layer:
// RMSNorm -> Q/K/V proj -> QK-Norm -> RoPE -> GQA -> O proj -> +residual
// RMSNorm -> gate/up proj -> SwiGLU -> down proj -> +residual
// Final: RMSNorm
#pragma once
#include "ggml.h"
#include "ggml-backend.h"
#include "backend.h"
#include "gguf_weights.h"
#include <cmath>
#include <cstdio>
#include <cstring>
#include <string>
#include <vector>
#define QWEN3_MAX_LAYERS 32
// Config
struct Qwen3Config {
int hidden_size; // H
int intermediate_size; // FFN inner dim
int n_heads; // Nh (query heads)
int n_kv_heads; // Nkv (key/value heads, for GQA)
int head_dim; // D = H / Nh
int n_layers;
float rope_theta;
float rms_norm_eps;
bool is_causal; // true for text encoder, false for lyric/timbre
};
// Per-layer weights
struct Qwen3Layer {
struct ggml_tensor * input_layernorm; // [H]
struct ggml_tensor * post_attn_layernorm; // [H]
struct ggml_tensor * q_proj; // [H, Nh*D] ggml = [Nh*D, H] PyTorch
struct ggml_tensor * k_proj; // [H, Nkv*D]
struct ggml_tensor * v_proj; // [H, Nkv*D]
struct ggml_tensor * o_proj; // [Nh*D, H]
struct ggml_tensor * q_norm; // [D]
struct ggml_tensor * k_norm; // [D]
struct ggml_tensor * gate_proj; // [H, FFN]
struct ggml_tensor * up_proj; // [H, FFN]
struct ggml_tensor * down_proj; // [FFN, H]
};
// Standalone model (text encoder)
struct Qwen3GGML {
Qwen3Config cfg;
Qwen3Layer layers[QWEN3_MAX_LAYERS];
// Embedding: vocab lookup table [H, vocab_size] in ggml
struct ggml_tensor * embed_tokens; // [H, V]
struct ggml_tensor * final_norm; // [H]
// Backend
ggml_backend_t backend;
ggml_backend_t cpu_backend;
ggml_backend_sched_t sched;
WeightCtx wctx;
};
// Helpers (pure graph ops, no side effects)
static struct ggml_tensor * qwen3_f32(struct ggml_context * ctx, struct ggml_tensor * t) {
if (t->type == GGML_TYPE_F32) return t;
return ggml_cast(ctx, t, GGML_TYPE_F32);
}
static struct ggml_tensor * qwen3_linear(struct ggml_context * ctx,
struct ggml_tensor * w,
struct ggml_tensor * x) {
return ggml_mul_mat(ctx, w, x);
}
static struct ggml_tensor * qwen3_linear_bias(struct ggml_context * ctx,
struct ggml_tensor * w,
struct ggml_tensor * b,
struct ggml_tensor * x) {
struct ggml_tensor * out = ggml_mul_mat(ctx, w, x);
return ggml_add(ctx, out, qwen3_f32(ctx, b));
}
static struct ggml_tensor * qwen3_rms_norm(struct ggml_context * ctx,
struct ggml_tensor * x,
struct ggml_tensor * w,
float eps) {
struct ggml_tensor * n = ggml_rms_norm(ctx, x, eps);
return ggml_mul(ctx, n, qwen3_f32(ctx, w));
}
// Graph builders
// These build sub-graphs and return output tensors.
// They operate on ggml layout: [H, S] for hidden states.
// Self-attention: norm_in [H, S] -> attn_out [H, S]
static struct ggml_tensor * qwen3_build_self_attn(
struct ggml_context * ctx,
const Qwen3Config & c,
Qwen3Layer * ly,
struct ggml_tensor * x, // [H, S]
struct ggml_tensor * positions, // [S] int32
struct ggml_tensor * mask, // [S, S] or NULL
int S) {
int D = c.head_dim;
int Nh = c.n_heads;
int Nkv = c.n_kv_heads;
// 1) Q/K/V projections
struct ggml_tensor * q = qwen3_linear(ctx, ly->q_proj, x); // [Nh*D, S]
struct ggml_tensor * k = qwen3_linear(ctx, ly->k_proj, x); // [Nkv*D, S]
struct ggml_tensor * v = qwen3_linear(ctx, ly->v_proj, x); // [Nkv*D, S]
// 2) Reshape to heads: [X*D, S] -> [D, X, S]
q = ggml_reshape_3d(ctx, q, D, Nh, S);
k = ggml_reshape_3d(ctx, k, D, Nkv, S);
v = ggml_reshape_3d(ctx, v, D, Nkv, S);
// 3) QK-Norm: per-head RMSNorm on D
q = ggml_rms_norm(ctx, q, c.rms_norm_eps);
q = ggml_mul(ctx, q, qwen3_f32(ctx, ly->q_norm));
k = ggml_rms_norm(ctx, k, c.rms_norm_eps);
k = ggml_mul(ctx, k, qwen3_f32(ctx, ly->k_norm));
// 4) RoPE
// ggml pitfall: mode=2 (NEOX half-split [i, i+D/2]), NOT mode=0 (consecutive [2i, 2i+1])
// Python ref: rope_batch_kernel pairs ptr[d] with ptr[d+half] = NEOX
q = ggml_rope_ext(ctx, q, positions, NULL,
D, 2, 0, c.rope_theta, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f);
k = ggml_rope_ext(ctx, k, positions, NULL,
D, 2, 0, c.rope_theta, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f);
// 5) Permute for flash_attn_ext: [D, X, S] -> [D, S, X]
q = ggml_permute(ctx, q, 0, 2, 1, 3);
k = ggml_permute(ctx, k, 0, 2, 1, 3);
v = ggml_permute(ctx, v, 0, 2, 1, 3);
// 6) Flash attention (handles GQA)
float scale = 1.0f / sqrtf((float)D);
struct ggml_tensor * attn = ggml_flash_attn_ext(ctx, q, k, v, mask, scale, 0.0f, 0.0f);
ggml_flash_attn_ext_set_prec(attn, GGML_PREC_F32); // F32 accumulation
// 7) Reshape back: [D, Nh, S] -> [Nh*D, S]
attn = ggml_reshape_2d(ctx, attn, Nh * D, S);
// 8) O projection
return qwen3_linear(ctx, ly->o_proj, attn);
}
// MLP: SwiGLU
static struct ggml_tensor * qwen3_build_mlp(
struct ggml_context * ctx,
Qwen3Layer * ly,
struct ggml_tensor * x, // [H, S]
int S) {
(void)S;
struct ggml_tensor * gate = qwen3_linear(ctx, ly->gate_proj, x);
struct ggml_tensor * up = qwen3_linear(ctx, ly->up_proj, x);
struct ggml_tensor * ff = ggml_swiglu_split(ctx, gate, up);
return qwen3_linear(ctx, ly->down_proj, ff);
}
// Single layer: input [H, S] -> output [H, S]
static struct ggml_tensor * qwen3_build_layer(
struct ggml_context * ctx,
const Qwen3Config & c,
Qwen3Layer * ly,
struct ggml_tensor * hidden,
struct ggml_tensor * positions,
struct ggml_tensor * mask,
int S) {
// Self-attention block
struct ggml_tensor * norm = qwen3_rms_norm(ctx, hidden, ly->input_layernorm, c.rms_norm_eps);
struct ggml_tensor * attn = qwen3_build_self_attn(ctx, c, ly, norm, positions, mask, S);
hidden = ggml_add(ctx, hidden, attn);
// MLP block
norm = qwen3_rms_norm(ctx, hidden, ly->post_attn_layernorm, c.rms_norm_eps);
struct ggml_tensor * mlp = qwen3_build_mlp(ctx, ly, norm, S);
hidden = ggml_add(ctx, hidden, mlp);
return hidden;
}
// Full N-layer stack: input [H, S] -> output [H, S] (post final-norm)
static struct ggml_tensor * qwen3_build_layers(
struct ggml_context * ctx,
const Qwen3Config & c,
Qwen3Layer * layers,
struct ggml_tensor * final_norm_w,
struct ggml_tensor * hidden,
struct ggml_tensor * positions,
struct ggml_tensor * mask,
int S) {
for (int i = 0; i < c.n_layers; i++) {
hidden = qwen3_build_layer(ctx, c, &layers[i], hidden, positions, mask, S);
}
return qwen3_rms_norm(ctx, hidden, final_norm_w, c.rms_norm_eps);
}
// Loading
static void qwen3_load_layer(WeightCtx * wctx, const GGUFModel & gf,
Qwen3Layer * ly, const std::string & prefix) {
ly->input_layernorm = gf_load_tensor_f32(wctx, gf, prefix + ".input_layernorm.weight");
ly->post_attn_layernorm = gf_load_tensor_f32(wctx, gf, prefix + ".post_attention_layernorm.weight");
ly->q_proj = gf_load_tensor(wctx, gf, prefix + ".self_attn.q_proj.weight");
ly->k_proj = gf_load_tensor(wctx, gf, prefix + ".self_attn.k_proj.weight");
ly->v_proj = gf_load_tensor(wctx, gf, prefix + ".self_attn.v_proj.weight");
ly->o_proj = gf_load_tensor(wctx, gf, prefix + ".self_attn.o_proj.weight");
ly->q_norm = gf_load_tensor_f32(wctx, gf, prefix + ".self_attn.q_norm.weight");
ly->k_norm = gf_load_tensor_f32(wctx, gf, prefix + ".self_attn.k_norm.weight");
ly->gate_proj = gf_load_tensor(wctx, gf, prefix + ".mlp.gate_proj.weight");
ly->up_proj = gf_load_tensor(wctx, gf, prefix + ".mlp.up_proj.weight");
ly->down_proj = gf_load_tensor(wctx, gf, prefix + ".mlp.down_proj.weight");
}
// Backend init
static void qwen3_init_backend(Qwen3GGML * m) {
BackendPair bp = backend_init("TextEncoder");
m->backend = bp.backend;
m->cpu_backend = bp.cpu_backend;
m->sched = backend_sched_new(bp, 4096);
}
// Load standalone text encoder (Qwen3-Embedding) from GGUF
// gguf_path: path to the .gguf file
static bool qwen3_load_text_encoder(Qwen3GGML * m, const char * gguf_path) {
m->cfg = {
/*hidden_size*/ 1024,
/*intermediate_size*/ 3072,
/*n_heads*/ 16,
/*n_kv_heads*/ 8,
/*head_dim*/ 128,
/*n_layers*/ 28,
/*rope_theta*/ 1000000.0f,
/*rms_norm_eps*/ 1e-6f,
/*is_causal*/ true,
};
GGUFModel gf;
if (!gf_load(&gf, gguf_path)) {
fprintf(stderr, "[Load] FATAL: cannot load %s\n", gguf_path);
return false;
}
// embed(1) + 28 layers * 11 weights + final_norm(1) = 310
int n_tensors = 1 + m->cfg.n_layers * 11 + 1;
wctx_init(&m->wctx, n_tensors);
m->embed_tokens = gf_load_tensor(&m->wctx, gf, "embed_tokens.weight");
m->final_norm = gf_load_tensor_f32(&m->wctx, gf, "norm.weight");
for (int i = 0; i < m->cfg.n_layers; i++) {
char prefix[64];
snprintf(prefix, sizeof(prefix), "layers.%d", i);
qwen3_load_layer(&m->wctx, gf, &m->layers[i], prefix);
}
if (!wctx_alloc(&m->wctx, m->backend)) {
gf_close(&gf);
return false;
}
gf_close(&gf);
fprintf(stderr, "[Load] TextEncoder: %dL, H=%d, Nh=%d/%d\n",
m->cfg.n_layers, m->cfg.hidden_size, m->cfg.n_heads, m->cfg.n_kv_heads);
return true;
}
// Forward: token IDs -> hidden states
// token_ids: [S] int32 (CPU)
// output: [H * S] float (CPU, caller-allocated)
// Returns hidden states in ggml layout: ne[0]=H contiguous, S rows.
static void qwen3_forward(Qwen3GGML * m, const int * token_ids, int S, float * output) {
const Qwen3Config & c = m->cfg;
int H = c.hidden_size;
// Graph context
size_t ctx_size = 2048 * ggml_tensor_overhead() + ggml_graph_overhead();
struct ggml_init_params gp = { ctx_size, NULL, true };
struct ggml_context * ctx = ggml_init(gp);
struct ggml_cgraph * gf = ggml_new_graph_custom(ctx, 4096, false);
// Input: token IDs [S]
struct ggml_tensor * t_ids = ggml_new_tensor_1d(ctx, GGML_TYPE_I32, S);
ggml_set_name(t_ids, "token_ids");
ggml_set_input(t_ids);
// Embedding lookup: [H, V] x [S] -> [H, S]
struct ggml_tensor * hidden = ggml_get_rows(ctx, m->embed_tokens, t_ids);
// Positions: [S] int32 (0, 1, 2, ...)
struct ggml_tensor * positions = ggml_new_tensor_1d(ctx, GGML_TYPE_I32, S);
ggml_set_name(positions, "positions");
ggml_set_input(positions);
// Attention mask: causal uses fp16 mask, bidirectional uses NULL (no mask)
struct ggml_tensor * mask = NULL;
if (c.is_causal) {
mask = ggml_new_tensor_2d(ctx, GGML_TYPE_F16, S, S);
ggml_set_name(mask, "attn_mask");
ggml_set_input(mask);
}
// N layers + final norm
struct ggml_tensor * out = qwen3_build_layers(ctx, c, m->layers, m->final_norm,
hidden, positions, mask, S);
ggml_set_name(out, "output");
ggml_set_output(out);
ggml_build_forward_expand(gf, out);
// Allocate
ggml_backend_sched_alloc_graph(m->sched, gf);
// Set inputs
ggml_backend_tensor_set(t_ids, token_ids, 0, S * sizeof(int));
{
std::vector<int> pos_data(S);
for (int i = 0; i < S; i++) pos_data[i] = i;
ggml_backend_tensor_set(positions, pos_data.data(), 0, S * sizeof(int));
}
if (c.is_causal) {
std::vector<uint16_t> mask_data(S * S);
for (int i = 0; i < S; i++)
for (int j = 0; j < S; j++) {
float v = (j <= i) ? 0.0f : -INFINITY;
mask_data[i * S + j] = ggml_fp32_to_fp16(v);
}
ggml_backend_tensor_set(mask, mask_data.data(), 0, S * S * sizeof(uint16_t));
}
// Compute
ggml_backend_sched_graph_compute(m->sched, gf);
// Read output [H, S]
ggml_backend_tensor_get(out, output, 0, H * S * sizeof(float));
ggml_backend_sched_reset(m->sched);
ggml_free(ctx);
}
// CPU vocab lookup utility
// For lyric embedding: look up token IDs in text encoder's embed table (bf16 -> f32)
// GGUF keeps mmapped data alive. Output: [H, S] float (H contiguous per token).
//
// embed_data: pointer to bf16 weight data [vocab, H] in PyTorch layout (H contiguous per row)
// token_ids: [S] int32
// output: [H * S] float (ggml layout: H contiguous, S tokens)
static void qwen3_cpu_embed_lookup(const void * embed_data, int H,
const int * token_ids, int S,
float * output) {
const uint16_t * bf16 = (const uint16_t *)embed_data;
for (int s = 0; s < S; s++) {
int tok = token_ids[s];
const uint16_t * row = bf16 + (int64_t)tok * H;
float * dst = output + (int64_t)s * H;
for (int h = 0; h < H; h++) {
// bf16 to f32: shift left 16 bits
uint32_t bits = (uint32_t)row[h] << 16;
memcpy(&dst[h], &bits, 4);
}
}
}
// Free
static void qwen3_free(Qwen3GGML * m) {
if (m->sched) ggml_backend_sched_free(m->sched);
if (m->backend && m->backend != m->cpu_backend) ggml_backend_free(m->backend);
if (m->cpu_backend) ggml_backend_free(m->cpu_backend);
wctx_free(&m->wctx);
*m = {};
}

View file

@ -0,0 +1,305 @@
//
// request.cpp - AceStep request JSON read/write
//
// Flat JSON only (no nested objects). Handles string escaping for lyrics etc.
//
#include "./request.h"
#include <cstdlib>
#include <cstring>
#include <string>
#include <vector>
// Defaults (aligned with Python GenerationParams)
void request_init(AceRequest * r) {
r->caption = "";
r->lyrics = "";
r->instrumental = false;
r->bpm = 0;
r->duration = -1.0f;
r->keyscale = "";
r->timesignature = "";
r->vocal_language = "unknown";
r->task_type = "text2music";
r->seed = -1;
r->thinking = true;
r->lm_temperature = 0.85f;
r->lm_cfg_scale = 2.0f;
r->lm_top_p = 0.9f;
r->lm_negative_prompt = "NO USER INPUT";
r->audio_codes = "";
r->inference_steps = 8;
r->guidance_scale = 1.0f;
r->shift = 3.0f;
}
// JSON string escape / unescape
static std::string json_escape(const std::string & s) {
std::string out;
out.reserve(s.size() + 16);
for (char c : s) {
switch (c) {
case '"': out += "\\\""; break;
case '\\': out += "\\\\"; break;
case '\n': out += "\\n"; break;
case '\r': out += "\\r"; break;
case '\t': out += "\\t"; break;
default:
if ((unsigned char)c < 0x20) {
char buf[8];
snprintf(buf, sizeof(buf), "\\u%04x", (unsigned char)c);
out += buf;
} else {
out += c;
}
}
}
return out;
}
static std::string json_unescape(const char * s, size_t len) {
std::string out;
out.reserve(len);
for (size_t i = 0; i < len; i++) {
if (s[i] == '\\' && i + 1 < len) {
switch (s[++i]) {
case '"': out += '"'; break;
case '\\': out += '\\'; break;
case '/': out += '/'; break;
case 'n': out += '\n'; break;
case 'r': out += '\r'; break;
case 't': out += '\t'; break;
case 'u':
// \\uXXXX: parse 4 hex digits, emit as UTF-8 (ASCII subset only)
if (i + 4 < len) {
char hex[5] = {s[i+1], s[i+2], s[i+3], s[i+4], 0};
unsigned cp = (unsigned)strtoul(hex, nullptr, 16);
i += 4;
if (cp < 0x80) {
out += (char)cp;
} else if (cp < 0x800) {
out += (char)(0xC0 | (cp >> 6));
out += (char)(0x80 | (cp & 0x3F));
} else {
out += (char)(0xE0 | (cp >> 12));
out += (char)(0x80 | ((cp >> 6) & 0x3F));
out += (char)(0x80 | (cp & 0x3F));
}
}
break;
default: out += s[i]; break;
}
} else {
out += s[i];
}
}
return out;
}
// Minimal flat JSON parser
struct JsonPair {
std::string key;
std::string value; // raw value (unquoted strings are unescaped, numbers/bools as-is)
bool is_string;
};
static const char * skip_ws(const char * p) {
while (*p == ' ' || *p == '\t' || *p == '\n' || *p == '\r') p++;
return p;
}
// Parse a JSON string starting at p (must point to opening '"').
// Returns pointer past closing '"', or nullptr on error.
static const char * parse_string(const char * p, std::string * out) {
if (*p != '"') return nullptr;
p++;
const char * start = p;
while (*p && *p != '"') {
if (*p == '\\') {
p++;
if (!*p) return nullptr;
}
p++;
}
if (*p != '"') return nullptr;
*out = json_unescape(start, (size_t)(p - start));
return p + 1;
}
// Parse a JSON value (string, number, bool, null).
// Skips arrays/objects by bracket matching (for forward compat).
static const char * parse_value(const char * p, std::string * out, bool * is_str) {
*is_str = false;
if (*p == '"') {
*is_str = true;
return parse_string(p, out);
}
if (*p == '[' || *p == '{') {
// skip nested structure (not used, but don't choke)
char open = *p, close = (*p == '[') ? ']' : '}';
int depth = 1;
const char * start = p;
p++;
while (*p && depth > 0) {
if (*p == open) depth++;
else if (*p == close) depth--;
else if (*p == '"') {
// skip strings inside nested structure
std::string dummy;
p = parse_string(p, &dummy);
if (!p) return nullptr;
continue;
}
p++;
}
*out = std::string(start, (size_t)(p - start));
return p;
}
// number, bool, null
const char * start = p;
while (*p && *p != ',' && *p != '}' && *p != ']' &&
*p != ' ' && *p != '\t' && *p != '\n' && *p != '\r') p++;
*out = std::string(start, (size_t)(p - start));
return p;
}
static bool parse_json_flat(const char * json, std::vector<JsonPair> * pairs) {
const char * p = skip_ws(json);
if (*p != '{') return false;
p = skip_ws(p + 1);
while (*p && *p != '}') {
JsonPair kv;
p = parse_string(p, &kv.key);
if (!p) return false;
p = skip_ws(p);
if (*p != ':') return false;
p = skip_ws(p + 1);
p = parse_value(p, &kv.value, &kv.is_string);
if (!p) return false;
pairs->push_back(kv);
p = skip_ws(p);
if (*p == ',') p = skip_ws(p + 1);
}
return true;
}
// File I/O helpers
static std::string read_file(const char * path) {
FILE * f = fopen(path, "rb");
if (!f) return "";
fseek(f, 0, SEEK_END);
long sz = ftell(f);
fseek(f, 0, SEEK_SET);
std::string buf((size_t)sz, '\0');
size_t nr = fread(&buf[0], 1, (size_t)sz, f);
fclose(f);
if ((long)nr != sz) buf.resize(nr);
return buf;
}
// Public API
bool request_parse(AceRequest * r, const char * path) {
request_init(r);
std::string json = read_file(path);
if (json.empty()) {
fprintf(stderr, "[Request] ERROR: cannot read %s\n", path);
return false;
}
std::vector<JsonPair> pairs;
if (!parse_json_flat(json.c_str(), &pairs)) {
fprintf(stderr, "[Request] ERROR: malformed JSON in %s\n", path);
return false;
}
for (const auto & kv : pairs) {
const std::string & k = kv.key;
const std::string & v = kv.value;
// strings
if (k == "caption") r->caption = v;
else if (k == "lyrics") r->lyrics = v;
else if (k == "keyscale") r->keyscale = v;
else if (k == "timesignature") r->timesignature = v;
else if (k == "vocal_language") r->vocal_language = v;
else if (k == "task_type") r->task_type = v;
else if (k == "audio_codes") r->audio_codes = v;
else if (k == "lm_negative_prompt") r->lm_negative_prompt = v;
// ints
else if (k == "bpm") r->bpm = atoi(v.c_str());
else if (k == "seed") r->seed = atoi(v.c_str());
// floats
else if (k == "duration") r->duration = (float)atof(v.c_str());
else if (k == "lm_temperature") r->lm_temperature = (float)atof(v.c_str());
else if (k == "lm_cfg_scale") r->lm_cfg_scale = (float)atof(v.c_str());
else if (k == "lm_top_p") r->lm_top_p = (float)atof(v.c_str());
else if (k == "inference_steps") r->inference_steps = atoi(v.c_str());
else if (k == "guidance_scale") r->guidance_scale = (float)atof(v.c_str());
else if (k == "shift") r->shift = (float)atof(v.c_str());
// bools
else if (k == "thinking") r->thinking = (v == "true");
else if (k == "instrumental") r->instrumental = (v == "true");
// unknown keys: silently ignored (forward compat)
}
fprintf(stderr, "[Request] parsed %s (%zu fields)\n", path, pairs.size());
return true;
}
bool request_write(const AceRequest * r, const char * path) {
FILE * f = fopen(path, "w");
if (!f) {
fprintf(stderr, "[Request] ERROR: cannot write %s\n", path);
return false;
}
fprintf(f, "{\n");
fprintf(f, " \"caption\": \"%s\",\n", json_escape(r->caption).c_str());
fprintf(f, " \"lyrics\": \"%s\",\n", json_escape(r->lyrics).c_str());
if (r->instrumental)
fprintf(f, " \"instrumental\": true,\n");
fprintf(f, " \"bpm\": %d,\n", r->bpm);
fprintf(f, " \"duration\": %.1f,\n", r->duration);
fprintf(f, " \"keyscale\": \"%s\",\n", json_escape(r->keyscale).c_str());
fprintf(f, " \"timesignature\": \"%s\",\n", json_escape(r->timesignature).c_str());
fprintf(f, " \"vocal_language\": \"%s\",\n", json_escape(r->vocal_language).c_str());
fprintf(f, " \"task_type\": \"%s\",\n", json_escape(r->task_type).c_str());
fprintf(f, " \"seed\": %d,\n", r->seed);
fprintf(f, " \"thinking\": %s,\n", r->thinking ? "true" : "false");
fprintf(f, " \"lm_temperature\": %.2f,\n", r->lm_temperature);
fprintf(f, " \"lm_cfg_scale\": %.1f,\n", r->lm_cfg_scale);
fprintf(f, " \"lm_top_p\": %.2f,\n", r->lm_top_p);
fprintf(f, " \"lm_negative_prompt\": \"%s\",\n", json_escape(r->lm_negative_prompt).c_str());
fprintf(f, " \"inference_steps\": %d,\n", r->inference_steps);
fprintf(f, " \"guidance_scale\": %.1f,\n", r->guidance_scale);
fprintf(f, " \"shift\": %.1f,\n", r->shift);
// audio_codes last (no trailing comma)
fprintf(f, " \"audio_codes\": \"%s\"\n", json_escape(r->audio_codes).c_str());
fprintf(f, "}\n");
fclose(f);
fprintf(stderr, "[Request] wrote %s\n", path);
return true;
}
void request_dump(const AceRequest * r, FILE * f) {
fprintf(f, "[Request] task=%s thinking=%s seed=%d\n",
r->task_type.c_str(), r->thinking ? "true" : "false", r->seed);
fprintf(f, " caption: %.60s%s\n",
r->caption.c_str(), r->caption.size() > 60 ? "..." : "");
fprintf(f, " lyrics: %zu bytes\n", r->lyrics.size());
fprintf(f, " bpm=%d dur=%.0f key=%s ts=%s lang=%s\n",
r->bpm, r->duration, r->keyscale.c_str(),
r->timesignature.c_str(), r->vocal_language.c_str());
fprintf(f, " lm: temp=%.2f cfg=%.1f top_p=%.2f\n",
r->lm_temperature, r->lm_cfg_scale, r->lm_top_p);
fprintf(f, " dit: steps=%d guidance=%.1f shift=%.1f\n",
r->inference_steps, r->guidance_scale, r->shift);
fprintf(f, " audio_codes: %s\n",
r->audio_codes.empty() ? "(none)" : "(present)");
}

View file

@ -0,0 +1,57 @@
#pragma once
//
// request.h - AceStep generation request (JSON serialization)
//
// Pure data container + JSON read/write. Zero business logic.
// Aligned with Python GenerationParams (inference.py:39) and API /release_task.
//
#include <string>
#include <cstdio>
struct AceRequest {
// text content
std::string caption; // ""
std::string lyrics; // ""
bool instrumental; // false
// metadata (user-provided or LLM-enriched)
int bpm; // 0 = unset
float duration; // -1 = unset
std::string keyscale; // "" = unset
std::string timesignature; // "" = unset
std::string vocal_language; // "unknown"
// generation
std::string task_type; // "text2music"
int seed; // -1 = random
// LM control
bool thinking; // true
float lm_temperature; // 0.85
float lm_cfg_scale; // 2.0
float lm_top_p; // 0.9
std::string lm_negative_prompt; // "NO USER INPUT"
// codes (Python-compatible string: "3101,11837,27514,...")
// empty = text2music (silence context), non-empty = cover mode
std::string audio_codes; // ""
// DiT control (Python: inference_steps, guidance_scale, shift)
int inference_steps; // 8
float guidance_scale; // 7.0
float shift; // 1.0
};
// Initialize all fields to defaults (matches Python GenerationParams defaults)
void request_init(AceRequest * r);
// Parse JSON file into struct. Missing fields keep their defaults.
// Returns false on file error or malformed JSON.
bool request_parse(AceRequest * r, const char * path);
// Write struct to JSON file (overwrites). Returns false on file error.
bool request_write(const AceRequest * r, const char * path);
// Dump human-readable summary to stream (debug)
void request_dump(const AceRequest * r, FILE * f);

View file

@ -0,0 +1,221 @@
// tokenizer.h: FSQ + Detokenizer via ggml
//
// Decodes LM audio codes into context_latents for DiT:
// codes [T_5Hz] -> FSQ decode -> [T_5Hz, 6] -> project_out -> [T_5Hz, 2048]
// -> detokenizer (per token): embed + special_tokens broadcast + 2L encoder + proj_out
// -> [T_25Hz, 64] context_latents (T_25Hz = T_5Hz * 5)
//
// Weights live in the DiT GGUF (prefix "tokenizer." and "detokenizer.")
// Detokenizer reuses Qwen3 encoder infrastructure from qwen3.h
#pragma once
#include "qwen3.h"
// FSQ constants
static const int FSQ_NDIMS = 6;
static const int FSQ_LEVELS[6] = {8, 8, 8, 5, 5, 5};
// FSQ decode: integer index -> 6 normalized float values
// Each dimension: level_idx / ((L-1)/2) - 1.0 (maps to [-1, 1])
static void fsq_decode_index(int index, float * out) {
int stride = 1;
for (int d = 0; d < FSQ_NDIMS; d++) {
int L = FSQ_LEVELS[d];
int level_idx = (index / stride) % L;
float half_L = (float)(L - 1) / 2.0f;
out[d] = (float)level_idx / half_L - 1.0f;
stride *= L;
}
}
// Detokenizer config: same Qwen3 arch as lyric/timbre encoders, 2 layers
static Qwen3Config detok_config() {
return {
/*hidden_size*/ 2048,
/*intermediate_size*/ 6144,
/*n_heads*/ 16,
/*n_kv_heads*/ 8,
/*head_dim*/ 128,
/*n_layers*/ 2,
/*rope_theta*/ 1000000.0f,
/*rms_norm_eps*/ 1e-6f,
/*is_causal*/ false,
};
}
struct DetokGGML {
// FSQ project_out: Linear(6, 2048) + bias
struct ggml_tensor * fsq_proj_w; // [2048, 6]
struct ggml_tensor * fsq_proj_b; // [2048]
// Detokenizer
struct ggml_tensor * embed_w; // [2048, 2048]
struct ggml_tensor * embed_b; // [2048]
struct ggml_tensor * special_tok; // [2048, 5] (broadcast positional)
Qwen3Config cfg;
Qwen3Layer layers[2];
struct ggml_tensor * norm; // [2048]
struct ggml_tensor * proj_out_w; // [64, 2048]
struct ggml_tensor * proj_out_b; // [64]
ggml_backend_t backend;
ggml_backend_t cpu_backend;
ggml_backend_sched_t sched;
WeightCtx wctx;
};
// Load from DiT GGUF
static bool detok_ggml_load(DetokGGML * m, const char * gguf_path,
ggml_backend_t backend, ggml_backend_t cpu_backend) {
m->cfg = detok_config();
m->backend = backend;
m->cpu_backend = cpu_backend;
GGUFModel gf;
if (!gf_load(&gf, gguf_path)) {
fprintf(stderr, "[Load] FATAL: cannot load %s\n", gguf_path);
return false;
}
// FSQ(2) + embed(2) + special(1) + 2 layers x 11(22) + norm(1) + proj(2) = 30
wctx_init(&m->wctx, 30);
m->fsq_proj_w = gf_load_tensor(&m->wctx, gf, "tokenizer.quantizer.project_out.weight");
m->fsq_proj_b = gf_load_tensor(&m->wctx, gf, "tokenizer.quantizer.project_out.bias");
m->embed_w = gf_load_tensor(&m->wctx, gf, "detokenizer.embed_tokens.weight");
m->embed_b = gf_load_tensor(&m->wctx, gf, "detokenizer.embed_tokens.bias");
m->norm = gf_load_tensor(&m->wctx, gf, "detokenizer.norm.weight");
m->proj_out_w = gf_load_tensor(&m->wctx, gf, "detokenizer.proj_out.weight");
m->proj_out_b = gf_load_tensor(&m->wctx, gf, "detokenizer.proj_out.bias");
// special_tokens: GGUF [2048, 5, 1] (ggml order), reshape to [2048, 5]
m->special_tok = gf_load_tensor(&m->wctx, gf, "detokenizer.special_tokens");
for (int i = 0; i < m->cfg.n_layers; i++) {
char prefix[128];
snprintf(prefix, sizeof(prefix), "detokenizer.layers.%d", i);
qwen3_load_layer(&m->wctx, gf, &m->layers[i], prefix);
}
if (!wctx_alloc(&m->wctx, backend)) {
gf_close(&gf);
return false;
}
gf_close(&gf);
// Scheduler
ggml_backend_t backends[2] = { backend, cpu_backend };
int n = (backend == cpu_backend) ? 1 : 2;
m->sched = ggml_backend_sched_new(backends, NULL, n, 4096, false, true);
fprintf(stderr, "[Load] Detokenizer: FSQ(6->2048) + 2L encoder(S=5, 2048->64)\n");
return true;
}
// Decode LM audio codes -> context_latents
// codes: [T_5Hz] integer array
// context_out: [64 * T_25Hz] flat, caller allocates (T_25Hz = T_5Hz * 5)
// ggml layout [64, T_25Hz]: element (c, t) = data[t * 64 + c]
static int detok_ggml_decode(DetokGGML * m, const int * codes, int T_5Hz,
float * context_out) {
int T_25Hz = T_5Hz * 5;
int H = 2048;
int P = 5; // pool window: each 5Hz token -> 5 frames at 25Hz
// Step 1: FSQ decode all indices on CPU -> [T_5Hz, 6]
std::vector<float> fsq_decoded(T_5Hz * FSQ_NDIMS);
for (int g = 0; g < T_5Hz; g++)
fsq_decode_index(codes[g], fsq_decoded.data() + g * FSQ_NDIMS);
// Step 2: build ggml graph for one token
// input [6] -> project_out [2048] -> embed_tokens [2048]
// -> broadcast + special_tokens [2048, 5] -> 2L encoder -> norm -> proj_out [64, 5]
size_t ctx_size = ggml_tensor_overhead() * 512 + ggml_graph_overhead_custom(4096, false);
std::vector<uint8_t> ctx_buf(ctx_size);
struct ggml_init_params p = { ctx_size, ctx_buf.data(), true };
struct ggml_context * ctx = ggml_init(p);
// Input: one FSQ-decoded vector [6]
// ggml pitfall: [6] is ne[0]=6, matches project_out weight [2048, 6]
struct ggml_tensor * fsq_in = ggml_new_tensor_1d(ctx, GGML_TYPE_F32, FSQ_NDIMS);
ggml_set_name(fsq_in, "fsq_in");
ggml_set_input(fsq_in);
// project_out: [6] -> [2048]
struct ggml_tensor * quantized = ggml_mul_mat(ctx, m->fsq_proj_w, fsq_in);
quantized = ggml_add(ctx, quantized, qwen3_f32(ctx, m->fsq_proj_b));
// embed_tokens: [2048] -> [2048]
struct ggml_tensor * embedded = ggml_mul_mat(ctx, m->embed_w,
ggml_reshape_2d(ctx, quantized, H, 1));
embedded = ggml_add(ctx, embedded, qwen3_f32(ctx, ggml_reshape_2d(ctx, m->embed_b, H, 1)));
// Broadcast [2048, 1] -> [2048, 5] + special_tokens [2048, 5]
// ggml pitfall: special_tokens loaded as BF16, cast to F32 for add
struct ggml_tensor * special_2d = ggml_reshape_2d(ctx, m->special_tok, H, P);
struct ggml_tensor * special_f32 = qwen3_f32(ctx, special_2d);
struct ggml_tensor * hidden = ggml_add(ctx, ggml_repeat(ctx, embedded, special_f32),
special_f32);
// Position indices for RoPE: [0, 1, 2, 3, 4]
struct ggml_tensor * positions = ggml_new_tensor_1d(ctx, GGML_TYPE_I32, P);
ggml_set_name(positions, "detok_pos");
ggml_set_input(positions);
// 2L encoder + norm (non-causal, no mask needed at S=5)
hidden = qwen3_build_layers(ctx, m->cfg, m->layers, m->norm,
hidden, positions, NULL, P);
// proj_out: [2048, 5] -> [64, 5]
struct ggml_tensor * output = ggml_mul_mat(ctx, m->proj_out_w, hidden);
output = ggml_add(ctx, output, qwen3_f32(ctx, ggml_reshape_2d(ctx, m->proj_out_b, 64, 1)));
ggml_set_name(output, "detok_out");
ggml_set_output(output);
struct ggml_cgraph * gf = ggml_new_graph_custom(ctx, 4096, false);
ggml_build_forward_expand(gf, output);
if (!ggml_backend_sched_alloc_graph(m->sched, gf)) {
fprintf(stderr, "[Context] FATAL: graph alloc failed\n");
ggml_free(ctx);
return -1;
}
// Set positions once (constant: 0,1,2,3,4)
int pos_data[5] = {0, 1, 2, 3, 4};
ggml_backend_tensor_set(ggml_graph_get_tensor(gf, "detok_pos"),
pos_data, 0, P * sizeof(int));
struct ggml_tensor * t_in = ggml_graph_get_tensor(gf, "fsq_in");
struct ggml_tensor * t_out = ggml_graph_get_tensor(gf, "detok_out");
// Step 3: loop over T_5Hz tokens
struct ggml_tensor * t_pos = ggml_graph_get_tensor(gf, "detok_pos");
for (int g = 0; g < T_5Hz; g++) {
// Re-set positions every iteration (allocator may share buffer with intermediates)
ggml_backend_tensor_set(t_pos, pos_data, 0, P * sizeof(int));
ggml_backend_tensor_set(t_in, fsq_decoded.data() + g * FSQ_NDIMS,
0, FSQ_NDIMS * sizeof(float));
ggml_backend_sched_graph_compute(m->sched, gf);
// output [64, 5]: 5 frames of 64 channels
// context_out layout: [64, T_25Hz], frame t at offset t*64
ggml_backend_tensor_get(t_out, context_out + g * P * 64,
0, P * 64 * sizeof(float));
}
fprintf(stderr, "[Context] Decoded: %d codes -> %d frames (%.1fs @ 25Hz)\n",
T_5Hz, T_25Hz, (float)T_25Hz / 25.0f);
ggml_backend_sched_reset(m->sched);
ggml_free(ctx);
return T_25Hz;
}
// Free
static void detok_ggml_free(DetokGGML * m) {
if (m->sched) ggml_backend_sched_free(m->sched);
wctx_free(&m->wctx);
*m = {};
}

520
otherarch/acestep/vae.h Normal file
View file

@ -0,0 +1,520 @@
// vae.h: AutoencoderOobleck decoder (audio VAE) via ggml
//
// Architecture: conv1(64->2048,k=7) -> 5xblock(snake+convT+3xresunit) -> snake+conv2(128->2,k=7)
// ResUnit(ch, dil): skip=x -> snake->conv(k=7,dil)->snake->conv(k=1)->+skip
// Snake: x + sin^2(e^a * x) / e^b
// Weight norm fused at load: w = g*v/||v||
// Upsample: 10x6x4x4x2 = 1920x
#pragma once
#include "ggml.h"
#include "ggml-backend.h"
#include "gguf_weights.h"
#include "backend.h"
#include <cmath>
#include <cstdio>
#include <cstring>
#include <string>
#include <vector>
// Structs
struct VAEResUnit {
struct ggml_tensor * s1a, * s1b; // snake1 exp(alpha), exp(beta) [1, C]
struct ggml_tensor * c1w, * c1b; // conv1 fused [7, C, C], bias [C]
struct ggml_tensor * s2a, * s2b; // snake2
struct ggml_tensor * c2w, * c2b; // conv2 fused [1, C, C], bias [C]
int dilation;
};
struct VAEBlock {
struct ggml_tensor * sa, * sb; // snake exp(a/b) [1, in_ch]
struct ggml_tensor * ctw, * ctb; // conv_transpose fused [K, out_ch, in_ch], bias [out_ch]
int in_ch, out_ch, stride, kernel;
VAEResUnit ru[3];
};
struct VAEGGML {
struct ggml_tensor * c1w, * c1b; // conv1 [7, 64, 2048], bias [2048]
VAEBlock blk[5];
struct ggml_tensor * sa, * sb; // final snake [1, 128]
struct ggml_tensor * c2w; // conv2 [7, 128, 2] (no bias)
ggml_backend_t backend;
ggml_backend_t cpu_backend;
ggml_backend_sched_t sched;
ggml_backend_buffer_t buf;
struct ggml_context * weight_ctx; // holds weight tensor metadata
// Graph cache for tiled decode (avoids rebuild per tile)
struct ggml_context * graph_ctx;
uint8_t * graph_buf; // heap-allocated backing for graph_ctx
struct ggml_cgraph * graph;
struct ggml_tensor * graph_input;
struct ggml_tensor * graph_output;
int graph_T; // cached T_latent (0 = no cache)
// Scratch buffer (reused across tiles, grown as needed)
std::vector<float> scratch_in; // transposed input [64 * T]
};
// Load helpers
// Fuse weight_norm: w = g*v/||v||, write f32 into pre-allocated ggml_tensor
// Works for both Conv1d [OC,IC,K] and ConvTranspose1d [IC,OC,K]:
// weight_norm normalizes over dim=0 (shape[0]), regardless of semantics.
static void vae_fuse_wn(struct ggml_tensor * dst, const GGUFModel & gf, const std::string & pfx) {
struct ggml_tensor * mv = ggml_get_tensor(gf.meta, (pfx + ".weight_v").c_str());
const uint16_t * g = (const uint16_t *)gf_get_data(gf, (pfx + ".weight_g").c_str());
const uint16_t * v = (const uint16_t *)gf_get_data(gf, (pfx + ".weight_v").c_str());
// PyTorch dim0 is ggml ne[n_dims-1]
int n_dims = ggml_n_dims(mv);
int dim0 = (int)mv->ne[n_dims - 1];
int fan = (int)(ggml_nelements(mv) / dim0);
std::vector<float> w(dim0 * fan);
for (int d = 0; d < dim0; d++) {
float gv = ggml_bf16_to_fp32(*(const ggml_bf16_t *)&g[d]);
float nsq = 0;
for (int i = 0; i < fan; i++) {
float vv = ggml_bf16_to_fp32(*(const ggml_bf16_t *)&v[d * fan + i]);
nsq += vv * vv;
}
float s = gv / (sqrtf(nsq) + 1e-12f);
for (int i = 0; i < fan; i++) {
float vv = ggml_bf16_to_fp32(*(const ggml_bf16_t *)&v[d * fan + i]);
w[d * fan + i] = vv * s;
}
}
if (dst->type == GGML_TYPE_F16) {
std::vector<ggml_fp16_t> w16(w.size());
ggml_fp32_to_fp16_row(w.data(), w16.data(), (int)w.size());
ggml_backend_tensor_set(dst, w16.data(), 0, w16.size() * sizeof(ggml_fp16_t));
} else {
ggml_backend_tensor_set(dst, w.data(), 0, w.size() * sizeof(float));
}
}
// Load bf16 snake param [1,C,1] -> exp -> f32 [1, C]
static void vae_load_snake(struct ggml_tensor * dst, const GGUFModel & gf, const std::string & name) {
struct ggml_tensor * mt = ggml_get_tensor(gf.meta, name.c_str());
int C = (int)mt->ne[1]; // PyTorch [1,C,1] -> ggml ne=[1,C,1], middle dim
const uint16_t * raw = (const uint16_t *)gf_get_data(gf, name.c_str());
std::vector<float> d(C);
for (int i = 0; i < C; i++)
d[i] = expf(ggml_bf16_to_fp32(*(const ggml_bf16_t *)&raw[i]));
ggml_backend_tensor_set(dst, d.data(), 0, C * sizeof(float));
}
// Load bf16 snake param [1,C,1] -> 1/exp -> f32 [1, C] (reciprocal for mul fusion)
static void vae_load_snake_inv(struct ggml_tensor * dst, const GGUFModel & gf, const std::string & name) {
struct ggml_tensor * mt = ggml_get_tensor(gf.meta, name.c_str());
int C = (int)mt->ne[1];
const uint16_t * raw = (const uint16_t *)gf_get_data(gf, name.c_str());
std::vector<float> d(C);
for (int i = 0; i < C; i++)
d[i] = 1.0f / expf(ggml_bf16_to_fp32(*(const ggml_bf16_t *)&raw[i]));
ggml_backend_tensor_set(dst, d.data(), 0, C * sizeof(float));
}
// Load bf16 bias [C] -> f32
static void vae_load_bias(struct ggml_tensor * dst, const GGUFModel & gf, const std::string & name) {
struct ggml_tensor * mt = ggml_get_tensor(gf.meta, name.c_str());
int C = (int)mt->ne[0]; // 1D: ne[0] = C
const uint16_t * raw = (const uint16_t *)gf_get_data(gf, name.c_str());
std::vector<float> d(C);
for (int i = 0; i < C; i++)
d[i] = ggml_bf16_to_fp32(*(const ggml_bf16_t *)&raw[i]);
ggml_backend_tensor_set(dst, d.data(), 0, C * sizeof(float));
}
// Load model
static void vae_ggml_load(VAEGGML * m, const char * path) {
GGUFModel gf = {};
if (!gf_load(&gf, path)) {
fprintf(stderr, "[VAE] FATAL: cannot load %s\n", path);
exit(1);
}
static const int strides[] = {10, 6, 4, 4, 2};
static const int in_ch[] = {2048, 1024, 512, 256, 128};
static const int out_ch[] = {1024, 512, 256, 128, 128};
static const int dilations[] = {1, 3, 9};
// Phase 1: create tensor metadata (no_alloc context)
size_t ctx_size = ggml_tensor_overhead() * 200;
struct ggml_init_params p = { ctx_size, NULL, true };
m->weight_ctx = ggml_init(p);
struct ggml_context * ctx = m->weight_ctx;
m->c1w = ggml_new_tensor_3d(ctx, GGML_TYPE_F16, 7, 64, 2048);
m->c1b = ggml_new_tensor_1d(ctx, GGML_TYPE_F32, 2048);
for (int i = 0; i < 5; i++) {
VAEBlock & b = m->blk[i];
b.in_ch = in_ch[i];
b.out_ch = out_ch[i];
b.stride = strides[i];
b.kernel = strides[i] * 2;
int C = out_ch[i];
b.sa = ggml_new_tensor_2d(ctx, GGML_TYPE_F32, 1, in_ch[i]);
b.sb = ggml_new_tensor_2d(ctx, GGML_TYPE_F32, 1, in_ch[i]);
b.ctw = ggml_new_tensor_3d(ctx, GGML_TYPE_F32, b.kernel, out_ch[i], in_ch[i]);
b.ctb = ggml_new_tensor_1d(ctx, GGML_TYPE_F32, out_ch[i]);
for (int r = 0; r < 3; r++) {
VAEResUnit & ru = b.ru[r];
ru.dilation = dilations[r];
ru.s1a = ggml_new_tensor_2d(ctx, GGML_TYPE_F32, 1, C);
ru.s1b = ggml_new_tensor_2d(ctx, GGML_TYPE_F32, 1, C);
ru.c1w = ggml_new_tensor_3d(ctx, GGML_TYPE_F16, 7, C, C);
ru.c1b = ggml_new_tensor_1d(ctx, GGML_TYPE_F32, C);
ru.s2a = ggml_new_tensor_2d(ctx, GGML_TYPE_F32, 1, C);
ru.s2b = ggml_new_tensor_2d(ctx, GGML_TYPE_F32, 1, C);
ru.c2w = ggml_new_tensor_3d(ctx, GGML_TYPE_F16, 1, C, C);
ru.c2b = ggml_new_tensor_1d(ctx, GGML_TYPE_F32, C);
}
}
m->sa = ggml_new_tensor_2d(ctx, GGML_TYPE_F32, 1, 128);
m->sb = ggml_new_tensor_2d(ctx, GGML_TYPE_F32, 1, 128);
m->c2w = ggml_new_tensor_3d(ctx, GGML_TYPE_F16, 7, 128, 2);
// Phase 2: allocate backend buffer (im2col grid Y fix enables long-sequence conv1d)
BackendPair bp = backend_init("VAE");
m->backend = bp.backend;
m->cpu_backend = bp.cpu_backend;
m->sched = backend_sched_new(bp, 8192);
m->buf = ggml_backend_alloc_ctx_tensors(ctx, m->backend);
fprintf(stderr, "[VAE] Backend: %s, Weight buffer: %.1f MB\n",
ggml_backend_name(m->backend),
(float)ggml_backend_buffer_get_size(m->buf) / (1024 * 1024));
// Phase 3: load & fuse weights
vae_fuse_wn(m->c1w, gf, "decoder.conv1");
vae_load_bias(m->c1b, gf, "decoder.conv1.bias");
for (int i = 0; i < 5; i++) {
VAEBlock & b = m->blk[i];
std::string blk_pfx = "decoder.block." + std::to_string(i);
vae_load_snake(b.sa, gf, blk_pfx + ".snake1.alpha");
vae_load_snake_inv(b.sb, gf, blk_pfx + ".snake1.beta");
vae_fuse_wn(b.ctw, gf, blk_pfx + ".conv_t1");
vae_load_bias(b.ctb, gf, blk_pfx + ".conv_t1.bias");
for (int r = 0; r < 3; r++) {
VAEResUnit & ru = b.ru[r];
std::string rp = blk_pfx + ".res_unit" + std::to_string(r + 1);
vae_load_snake(ru.s1a, gf, rp + ".snake1.alpha");
vae_load_snake_inv(ru.s1b, gf, rp + ".snake1.beta");
vae_fuse_wn(ru.c1w, gf, rp + ".conv1");
vae_load_bias(ru.c1b, gf, rp + ".conv1.bias");
vae_load_snake(ru.s2a, gf, rp + ".snake2.alpha");
vae_load_snake_inv(ru.s2b, gf, rp + ".snake2.beta");
vae_fuse_wn(ru.c2w, gf, rp + ".conv2");
vae_load_bias(ru.c2b, gf, rp + ".conv2.bias");
}
}
vae_load_snake(m->sa, gf, "decoder.snake1.alpha");
vae_load_snake_inv(m->sb, gf, "decoder.snake1.beta");
vae_fuse_wn(m->c2w, gf, "decoder.conv2");
fprintf(stderr, "[VAE] Loaded: 5 blocks, upsample=1920x\n");
gf_close(&gf);
}
// Graph building
// Snake activation: x + sin^2(exp_a * x) * inv_b
// x: [T, C], exp_a: [1, C], inv_b: [1, C] (pre-computed 1/exp(b))
static struct ggml_tensor * vae_snake(
struct ggml_context * ctx,
struct ggml_tensor * x,
struct ggml_tensor * exp_a,
struct ggml_tensor * inv_b) {
struct ggml_tensor * ax = ggml_mul(ctx, x, exp_a); // [T, C] (broadcast 1->T)
struct ggml_tensor * s = ggml_sin(ctx, ax); // sin(e^a * x)
struct ggml_tensor * s2 = ggml_sqr(ctx, s); // sin^2
struct ggml_tensor * d = ggml_mul(ctx, s2, inv_b); // * 1/e^b
return ggml_add(ctx, x, d); // x + ...
}
// Conv1d + bias: data [T, IC] -> [T_out, OC]
static struct ggml_tensor * vae_conv1d(
struct ggml_context * ctx,
struct ggml_tensor * w, // [K, IC, OC] (F16, pre-cast at load)
struct ggml_tensor * b, // [OC] or NULL
struct ggml_tensor * x, // [T, IC]
int stride, int padding, int dilation) {
struct ggml_tensor * y = ggml_conv_1d(ctx, w, x, stride, padding, dilation);
// ggml_conv_1d returns [OL, OC, N=1], squeeze to 2d
y = ggml_reshape_2d(ctx, y, y->ne[0], y->ne[1]);
if (b) {
// bias [OC] -> [1, OC] for broadcast over OL dimension
struct ggml_tensor * b2d = ggml_reshape_2d(ctx, b, 1, b->ne[0]);
y = ggml_add(ctx, y, b2d);
}
return y;
}
// ConvTranspose1d + bias
static struct ggml_tensor * vae_conv_t1d(
struct ggml_context * ctx,
struct ggml_tensor * w, // [K, out_ch, in_ch]
struct ggml_tensor * b, // [out_ch] or NULL
struct ggml_tensor * x, // [T, in_ch]
int stride, int padding) {
// ggml_conv_transpose_1d asserts p0==0, so we crop manually
struct ggml_tensor * y = ggml_conv_transpose_1d(ctx, w, x, stride, 0, 1);
// y is 4d: [OL, OC, 1, 1], squeeze to 2d
y = ggml_reshape_2d(ctx, y, y->ne[0], y->ne[1]);
// Crop padding from both sides: [OL, OC] -> [OL - 2*pad, OC]
if (padding > 0) {
y = ggml_view_2d(ctx, y, y->ne[0] - 2 * padding, y->ne[1],
y->nb[1], padding * sizeof(float));
}
if (b) {
struct ggml_tensor * b2d = ggml_reshape_2d(ctx, b, 1, b->ne[0]);
y = ggml_add(ctx, y, b2d);
}
return y;
}
// ResUnit forward
static struct ggml_tensor * vae_res_unit(
struct ggml_context * ctx,
VAEResUnit * ru,
struct ggml_tensor * x) { // [T, C]
struct ggml_tensor * skip = x;
// snake1 -> dilated conv(k=7) -> snake2 -> conv(k=1)
int pad = 3 * ru->dilation; // (k-1)*dil/2 = 3*dil
x = vae_snake(ctx, x, ru->s1a, ru->s1b);
x = vae_conv1d(ctx, ru->c1w, ru->c1b, x, 1, pad, ru->dilation);
x = vae_snake(ctx, x, ru->s2a, ru->s2b);
x = vae_conv1d(ctx, ru->c2w, ru->c2b, x, 1, 0, 1);
return ggml_add(ctx, skip, x);
}
// Build full VAE decode graph
// latent: [T_latent, 64] -> audio: [T_audio, 2]
static struct ggml_tensor * vae_ggml_build_graph(
struct ggml_context * ctx,
VAEGGML * m,
struct ggml_tensor * latent) { // [T, 64] input
// conv1: [T, 64] -> [T, 2048]
struct ggml_tensor * x = vae_conv1d(ctx, m->c1w, m->c1b, latent, 1, 3, 1);
// 5 decoder blocks
for (int i = 0; i < 5; i++) {
VAEBlock & b = m->blk[i];
// snake -> conv_transpose (upsample)
x = vae_snake(ctx, x, b.sa, b.sb);
int pad = (b.kernel - b.stride) / 2;
x = vae_conv_t1d(ctx, b.ctw, b.ctb, x, b.stride, pad);
// 3 res units
for (int r = 0; r < 3; r++)
x = vae_res_unit(ctx, &b.ru[r], x);
}
// Final: snake -> conv2(128->2, k=7, pad=3)
x = vae_snake(ctx, x, m->sa, m->sb);
x = vae_conv1d(ctx, m->c2w, NULL, x, 1, 3, 1);
return x; // [T_audio, 2]
}
// Core compute: ensure graph cached, set input, run. Returns T_audio or -1.
// Output remains in m->graph_output for caller to read as needed.
static int vae_ggml_compute(
VAEGGML * m,
const float * latent, // [T_full, 64] time-major
int T_latent, // window length to decode
int win_start = 0) { // offset into latent
// Build graph only when T_latent changes
if (m->graph_T != T_latent) {
if (m->graph_ctx) {
ggml_backend_sched_reset(m->sched);
ggml_free(m->graph_ctx);
free(m->graph_buf);
}
size_t ctx_size = ggml_tensor_overhead() * 1024 + ggml_graph_overhead_custom(8192, false);
m->graph_buf = (uint8_t *)malloc(ctx_size);
struct ggml_init_params p = { ctx_size, m->graph_buf, true };
struct ggml_context * ctx = ggml_init(p);
m->graph_input = ggml_new_tensor_2d(ctx, GGML_TYPE_F32, T_latent, 64);
ggml_set_name(m->graph_input, "vae_input");
ggml_set_input(m->graph_input);
m->graph_output = vae_ggml_build_graph(ctx, m, m->graph_input);
ggml_set_name(m->graph_output, "vae_output");
ggml_set_output(m->graph_output);
m->graph = ggml_new_graph_custom(ctx, 8192, false);
ggml_build_forward_expand(m->graph, m->graph_output);
if (!ggml_backend_sched_alloc_graph(m->sched, m->graph)) {
fprintf(stderr, "[VAE] FATAL: graph alloc failed\n");
ggml_free(ctx);
free(m->graph_buf);
m->graph_ctx = NULL;
m->graph_buf = NULL;
m->graph_T = 0;
return -1;
}
m->graph_ctx = ctx;
m->graph_T = T_latent;
fprintf(stderr, "[VAE] Graph: %d nodes, T_latent=%d\n",
ggml_graph_n_nodes(m->graph), T_latent);
}
// Extract window + transpose: [T, 64] time-major -> ggml [T, 64] channel-major
size_t in_size = 64 * T_latent;
if (m->scratch_in.size() < in_size)
m->scratch_in.resize(in_size);
for (int c = 0; c < 64; c++)
for (int t = 0; t < T_latent; t++)
m->scratch_in[c * T_latent + t] = latent[(win_start + t) * 64 + c];
ggml_backend_tensor_set(m->graph_input,
m->scratch_in.data(), 0, in_size * sizeof(float));
ggml_backend_sched_graph_compute(m->sched, m->graph);
return (int)m->graph_output->ne[0];
}
// Decode API: latent [T_latent, 64] -> audio [2, T_audio] flat.
// Returns T_audio (or -1 on error).
static int vae_ggml_decode(
VAEGGML * m,
const float * latent,
int T_latent,
float * audio_out,
int max_T_audio) {
int T_audio = T_latent * 1920;
if (T_audio > max_T_audio) {
fprintf(stderr, "[VAE] T_audio %d exceeds max %d\n", T_audio, max_T_audio);
return -1;
}
int T_out = vae_ggml_compute(m, latent, T_latent, 0);
if (T_out < 0) return -1;
ggml_backend_tensor_get(m->graph_output, audio_out, 0, T_out * 2 * sizeof(float));
fprintf(stderr, "[VAE] Decoded: T_latent=%d -> T_audio=%d (%.2fs @ 48kHz)\n",
T_latent, T_out, (float)T_out / 48000.0f);
return T_out;
}
// Tiled decode: overlap-discard chunking for bounded VRAM usage.
// Matches Python handler.py tiled_decode / _tiled_decode_gpu:
// stride = chunk_size - 2*overlap
// For each tile: decode latent window with overlap context, trim to core, concatenate.
// Default chunk=256, overlap=64 matches Python handler.py fallback defaults.
// Python auto-tunes chunk by VRAM: >=24GB->512, >=16GB->384, >=12GB->256, <12GB->128.
// Returns T_audio (total samples per channel) or -1 on error.
static int vae_ggml_decode_tiled(
VAEGGML * m,
const float * latent, // [T_latent, 64] flat time-major (DiT output layout)
int T_latent,
float * audio_out, // [2, T_audio] flat (caller allocs)
int max_T_audio,
int chunk_size = 256,
int overlap = 64) {
// Ensure positive stride (matches Python effective_overlap reduction)
while (chunk_size - 2 * overlap <= 0 && overlap > 0)
overlap /= 2;
// Short sequence: decode directly
if (T_latent <= chunk_size)
return vae_ggml_decode(m, latent, T_latent, audio_out, max_T_audio);
int stride = chunk_size - 2 * overlap;
int num_steps = (T_latent + stride - 1) / stride;
fprintf(stderr, "[VAE] Tiled decode: %d tiles (chunk=%d, overlap=%d, stride=%d)\n",
num_steps, chunk_size, overlap, stride);
float upsample_factor = 0.0f;
int audio_write_pos = 0;
for (int i = 0; i < num_steps; i++) {
// Core range in latent frames (the part we keep)
int core_start = i * stride;
int core_end = core_start + stride;
if (core_end > T_latent) core_end = T_latent;
// Window range with overlap context
int win_start = core_start - overlap;
if (win_start < 0) win_start = 0;
int win_end = core_end + overlap;
if (win_end > T_latent) win_end = T_latent;
int win_len = win_end - win_start;
// Compute tile (graph cached, extract+transpose fused)
int tile_T = vae_ggml_compute(m, latent, win_len, win_start);
if (tile_T < 0) {
fprintf(stderr, "[VAE] FATAL: tile %d decode failed\n", i);
return -1;
}
// Determine upsample factor from first tile
if (i == 0) {
upsample_factor = (float)tile_T / (float)win_len;
fprintf(stderr, "[VAE] Upsample factor: %.2f (expected ~1920)\n", upsample_factor);
}
// Compute trim in audio samples (matches Python int(round(...)))
int added_start = core_start - win_start;
int trim_start = (int)roundf((float)added_start * upsample_factor);
int added_end = win_end - core_end;
int trim_end = (int)roundf((float)added_end * upsample_factor);
int end_idx = (trim_end > 0) ? (tile_T - trim_end) : tile_T;
int core_len = end_idx - trim_start;
if (core_len <= 0) continue;
// Check output bounds
if (audio_write_pos + core_len > max_T_audio) {
fprintf(stderr, "[VAE] FATAL: tiled output exceeds max_T_audio\n");
return -1;
}
// Read trimmed ch0 and ch1 directly from backend tensor into final audio_out
// Layout: [ch0: tile_T floats, ch1: tile_T floats]
ggml_backend_tensor_get(m->graph_output,
audio_out + audio_write_pos,
trim_start * sizeof(float),
core_len * sizeof(float));
ggml_backend_tensor_get(m->graph_output,
audio_out + max_T_audio + audio_write_pos,
(tile_T + trim_start) * sizeof(float),
core_len * sizeof(float));
audio_write_pos += core_len;
}
// Compact ch1 from offset max_T_audio to offset audio_write_pos
memmove(audio_out + audio_write_pos,
audio_out + max_T_audio,
audio_write_pos * sizeof(float));
fprintf(stderr, "[VAE] Tiled decode done: %d tiles -> T_audio=%d (%.2fs @ 48kHz)\n",
num_steps, audio_write_pos, (float)audio_write_pos / 48000.0f);
return audio_write_pos;
}
// Free
static void vae_ggml_free(VAEGGML * m) {
if (m->graph_ctx) {
ggml_backend_sched_reset(m->sched);
ggml_free(m->graph_ctx);
free(m->graph_buf);
}
if (m->sched) ggml_backend_sched_free(m->sched);
if (m->buf) ggml_backend_buffer_free(m->buf);
if (m->weight_ctx) ggml_free(m->weight_ctx);
if (m->backend && m->backend != m->cpu_backend) ggml_backend_free(m->backend);
if (m->cpu_backend) ggml_backend_free(m->cpu_backend);
*m = {};
}

View file

@ -0,0 +1,74 @@
#pragma once
// weight_ctx.h: format-independent weight loading context for ggml backends
//
// Manages a ggml_context for weight tensors + their backend buffer.
// Used by gguf_weights.h for all model loaders.
//
// Usage:
// WeightCtx wctx;
// wctx_init(&wctx, n_tensors);
// ggml_tensor * w = <loader>_load_tensor(&wctx, source, "name");
// wctx_alloc(&wctx, backend);
#include "ggml.h"
#include "ggml-backend.h"
#include <cstdio>
#include <cstddef>
#include <vector>
struct WeightCtx {
struct ggml_context * ctx;
ggml_backend_buffer_t buffer;
struct PendingCopy {
struct ggml_tensor * tensor;
const void * src;
size_t nbytes;
size_t offset; // byte offset into dst tensor (0 for regular loads)
};
std::vector<PendingCopy> pending;
// Staging buffers for type-converted data (kept alive until wctx_alloc)
std::vector<std::vector<float>> staging;
};
static void wctx_init(WeightCtx * wctx, int n_tensors) {
size_t ctx_size = (size_t)n_tensors * ggml_tensor_overhead() + 1024;
struct ggml_init_params params = {
/*.mem_size =*/ ctx_size,
/*.mem_buffer =*/ NULL,
/*.no_alloc =*/ true,
};
wctx->ctx = ggml_init(params);
wctx->buffer = NULL;
wctx->pending.clear();
wctx->pending.reserve(n_tensors);
}
static bool wctx_alloc(WeightCtx * wctx, ggml_backend_t backend) {
wctx->buffer = ggml_backend_alloc_ctx_tensors(wctx->ctx, backend);
if (!wctx->buffer) {
fprintf(stderr, "[WeightCtx] FATAL: failed to allocate backend buffer\n");
return false;
}
// Mark as weight buffer so ggml_backend_sched assigns ops to the correct
// backend based on weight location (avoids fallback through expansion).
ggml_backend_buffer_set_usage(wctx->buffer, GGML_BACKEND_BUFFER_USAGE_WEIGHTS);
size_t total = 0;
for (auto & pc : wctx->pending) {
ggml_backend_tensor_set(pc.tensor, pc.src, pc.offset, pc.nbytes);
total += pc.nbytes;
}
fprintf(stderr, "[WeightCtx] Loaded %zu tensors, %.1f MB into backend\n",
wctx->pending.size(), (float)total / (1024 * 1024));
wctx->pending.clear();
wctx->staging.clear();
return true;
}
static void wctx_free(WeightCtx * wctx) {
if (wctx->buffer) ggml_backend_buffer_free(wctx->buffer);
if (wctx->ctx) ggml_free(wctx->ctx);
wctx->buffer = NULL;
wctx->ctx = NULL;
}