Initial release

This commit is contained in:
civ
2026-08-16 18:24:52 +07:00
commit 876886a39a
13244 changed files with 2353959 additions and 0 deletions
+178
View File
@@ -0,0 +1,178 @@
// ace-lm.cpp: ACE-Step LLM CLI
// Thin wrapper: parses args, scans the model registry, calls pipeline-lm,
// writes output files. The model to use comes from request.lm_model, the
// registry resolves it to a GGUF path under --models <dir>.
#include "model-registry.h"
#include "model-store.h"
#include "pipeline-lm.h"
#include "request.h"
#include "task-types.h"
#include "version.h"
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <string>
#include <vector>
static void usage(const char * prog) {
AceLmParams d;
ace_lm_default_params(&d);
fprintf(stderr, "acestep.cpp %s\n\n", ACE_VERSION);
fprintf(stderr,
"Usage: %s --models <dir> --request <json> [options]\n"
"\n"
"Required:\n"
" --models <dir> Directory of GGUF model files\n"
" --request <json> Input request JSON (carries lm_model)\n"
"\n"
"Debug:\n"
" --max-seq <N> KV cache size (default: %d)\n"
" --no-fsm Disable FSM constrained decoding\n"
" --no-fa Disable flash attention\n"
" --no-batch-cfg Split CFG into two separate forwards\n"
" --clamp-fp16 Clamp hidden states to FP16 range\n"
" --dump-logits <path> Dump prefill logits (binary f32)\n"
" --dump-tokens <path> Dump prompt token IDs (CSV)\n",
prog, d.max_seq);
}
int main(int argc, char ** argv) {
AceLmParams params;
ace_lm_default_params(&params);
const char * models_dir = NULL;
const char * request_path = NULL;
const char * dump_logits = NULL;
const char * dump_tokens = NULL;
if (argc < 2) {
usage(argv[0]);
return 1;
}
for (int i = 1; i < argc; i++) {
if (!strcmp(argv[i], "--models") && i + 1 < argc) {
models_dir = argv[++i];
} else if (!strcmp(argv[i], "--request") && i + 1 < argc) {
request_path = argv[++i];
} else if (!strcmp(argv[i], "--max-seq") && i + 1 < argc) {
params.max_seq = atoi(argv[++i]);
} else if (!strcmp(argv[i], "--no-fsm")) {
params.use_fsm = false;
} else if (!strcmp(argv[i], "--no-fa")) {
params.use_fa = false;
} else if (!strcmp(argv[i], "--no-batch-cfg")) {
params.use_batch_cfg = false;
} else if (!strcmp(argv[i], "--clamp-fp16")) {
params.clamp_fp16 = true;
} else if (!strcmp(argv[i], "--dump-logits") && i + 1 < argc) {
dump_logits = argv[++i];
} else if (!strcmp(argv[i], "--dump-tokens") && i + 1 < argc) {
dump_tokens = argv[++i];
} else if (!strcmp(argv[i], "--help") || !strcmp(argv[i], "-h")) {
usage(argv[0]);
return 0;
} else {
fprintf(stderr, "Unknown option: %s\n", argv[i]);
usage(argv[0]);
return 1;
}
}
if (!models_dir) {
fprintf(stderr, "[CLI] ERROR: --models required\n");
usage(argv[0]);
return 1;
}
if (!request_path) {
fprintf(stderr, "[CLI] ERROR: --request required\n");
usage(argv[0]);
return 1;
}
// Parse input request
AceRequest req;
if (!request_parse(&req, request_path)) {
return 1;
}
request_dump(&req, stderr);
// Scan the registry and resolve lm_model. Missing or empty lm_model falls
// to the first LM in the registry, matching server default behavior.
ModelRegistry registry;
if (!registry_scan(&registry, models_dir)) {
fprintf(stderr, "[Ace-LM] FATAL: cannot scan --models %s\n", models_dir);
return 1;
}
if (registry.lm.empty()) {
fprintf(stderr, "[Ace-LM] FATAL: no LM models found under %s\n", models_dir);
return 1;
}
const ModelEntry * lm_entry =
req.lm_model.empty() ? &registry.lm[0] : registry_find(registry.lm, req.lm_model.c_str());
if (!lm_entry) {
fprintf(stderr, "[Ace-LM] FATAL: lm_model '%s' not found in registry\n", req.lm_model.c_str());
return 1;
}
params.model_path = lm_entry->path.c_str();
// lm_batch_size from JSON (clamped to 1..9)
int lm_batch_size = req.lm_batch_size;
if (lm_batch_size < 1) {
lm_batch_size = 1;
} else if (lm_batch_size > 9) {
fprintf(stderr, "[Ace-LM] WARNING: lm_batch_size %d clamped to 9\n", lm_batch_size);
lm_batch_size = 9;
}
// Resolve lm_mode string to integer mode used by ace_lm_generate.
int mode;
if (req.lm_mode == LM_MODE_NAME_GENERATE) {
mode = LM_MODE_GENERATE;
} else if (req.lm_mode == LM_MODE_NAME_INSPIRE) {
mode = LM_MODE_INSPIRE;
} else if (req.lm_mode == LM_MODE_NAME_FORMAT) {
mode = LM_MODE_FORMAT;
} else {
fprintf(stderr, "[Ace-LM] FATAL: invalid lm_mode '%s' (use: generate, inspire, format)\n", req.lm_mode.c_str());
return 1;
}
// Load model (KV cache sized for request batch)
params.max_batch = lm_batch_size;
ModelStore * store = store_create(EVICT_STRICT);
AceLm * ctx = ace_lm_load(store, &params);
if (!ctx) {
store_free(store);
return 1;
}
// Generate
std::vector<AceRequest> out(lm_batch_size);
if (ace_lm_generate(ctx, &req, lm_batch_size, out.data(), dump_logits, dump_tokens, NULL, NULL, mode) != 0) {
ace_lm_free(ctx);
store_free(store);
return 1;
}
// Write output files: request.json -> request0.json, request1.json, ...
std::string base(request_path);
std::string ext = ".json";
size_t dot = base.rfind('.');
if (dot != std::string::npos) {
ext = base.substr(dot);
base = base.substr(0, dot);
}
for (int b = 0; b < lm_batch_size; b++) {
char path[512];
snprintf(path, sizeof(path), "%s%d%s", base.c_str(), b, ext.c_str());
request_write(&out[b], path);
}
ace_lm_free(ctx);
store_free(store);
return 0;
}
+147
View File
@@ -0,0 +1,147 @@
# ace-midi-validate.py — reference dumps for the ace-midi GGML port (Phase 1/2)
#
# Runs inside the kept MuScriptor oracle venv (CPU torch, fp32 — deterministic):
# server\data\muscriptor\venv\Scripts\python.exe engine\tools\ace-midi-validate.py \
# --weights server\data\models\muscriptor\small\model.safetensors \
# --out server\data\muscriptor\validation
#
# Dumps (little-endian f32 raw + manifest.json):
# wav.bin [80000] deterministic synthetic 5 s chunk (sines + click train)
# prefix.bin [T_prefix,dim] conditioning prefix embeddings in final sequence order
# (exactly what LMModel.forward prepends before BOS)
# logits_bos.bin [card] forward([[BOS]], conditions, first_step=True) last-step logits
# tokens_ref.json greedy token stream for the chunk (max 256, EOS-stripped)
#
# See docs/plans/muscriptor-cpp-port.md §6 (validation plan).
import argparse
import json
import math
from pathlib import Path
import torch
from muscriptor.transcription_model import TranscriptionModel
from muscriptor.modules.streaming import init_states
def synth_wav(n: int = 80_000, sr: int = 16_000) -> torch.Tensor:
"""Deterministic, spectrally busy test signal: three sines + a click train."""
t = torch.arange(n, dtype=torch.float32) / sr
wav = (
0.40 * torch.sin(2 * math.pi * 220.0 * t)
+ 0.25 * torch.sin(2 * math.pi * 554.37 * t) # C#5
+ 0.15 * torch.sin(2 * math.pi * 1318.5 * t) # E6
)
# 4 Hz click train for onset structure
clicks = ((torch.arange(n) % (sr // 4)) < 32).float() * 0.5
return (wav + clicks).clamp(-1.0, 1.0)
def synth_wav_multi(sr: int = 16_000) -> torch.Tensor:
"""13 s / 3-chunk signal with content changes across chunk boundaries and
sustained tones crossing them — exercises tie prologues + prelude forcing.
Last chunk is partial (3 s) to exercise the mel length mask."""
n = 13 * sr
t = torch.arange(n, dtype=torch.float32) / sr
wav = 0.35 * torch.sin(2 * math.pi * 220.0 * t) # sustained A3 throughout
seg2 = (t >= 4.0) & (t < 9.5) # crosses the 5 s boundary
wav = wav + 0.30 * torch.sin(2 * math.pi * 329.63 * t) * seg2.float()
seg3 = t >= 8.0 # crosses the 10 s boundary
wav = wav + 0.25 * torch.sin(2 * math.pi * 493.88 * t) * seg3.float()
clicks = ((torch.arange(n) % (sr // 2)) < 32).float() * 0.4
return (wav + clicks).clamp(-1.0, 1.0)
def main() -> None:
ap = argparse.ArgumentParser()
ap.add_argument("--weights", required=True)
ap.add_argument("--out", required=True)
ap.add_argument("--max-tokens", type=int, default=256)
args = ap.parse_args()
out = Path(args.out)
out.mkdir(parents=True, exist_ok=True)
tm = TranscriptionModel.load_model(weights_path=Path(args.weights), device="cpu")
model = tm._model
model.eval()
wav = synth_wav()
wav.numpy().tofile(out / "wav.bin")
conds = tm._build_conditions(wav.unsqueeze(0).to(tm._device))
prepared = model.condition_provider.tokenize(conds)
cond_tensors = model.condition_provider(prepared)
# Replicate LMModel.forward's prepend loop to get the prefix in final order
dim = model.dim
prefix = torch.zeros(1, 0, dim)
for cond, _mask in cond_tensors.values():
prefix = torch.cat([cond, prefix], dim=1)
prefix[0].detach().float().numpy().tofile(out / "prefix.bin")
# Single forward: [BOS] with conditions prepended (prefill parity target)
with torch.no_grad():
seq = torch.tensor([[model.initial_token_id]], dtype=torch.long)
state = init_states(model, batch_size=1, sequence_length=prefix.shape[1] + 16)
logits = model(seq, cond_tensors, first_step=True, model_state=state)
logits[0, -1].detach().float().numpy().tofile(out / "logits_bos.bin")
# Greedy token stream for the chunk (Phase 2 target)
tokens: list[int] = []
eos = tm._tokenizer.eos_id
with torch.no_grad():
for step in model.generate(
conditions=conds,
max_gen_len=args.max_tokens,
use_sampling=False,
early_stop_on_token=eos,
):
tok = int(step[0].item())
if tok == eos:
break
tokens.append(tok)
(out / "tokens_ref.json").write_text(json.dumps(tokens))
# ── Phase 3 references: multi-chunk events + MIDI (prelude forcing on) ──
from muscriptor.events import NoteStartEvent, NoteEndEvent, ProgressEvent
wav15 = synth_wav_multi()
wav15.numpy().tofile(out / "wav15.bin")
events_json = []
collected = []
for ev in tm.transcribe((wav15.unsqueeze(0), 16_000)):
collected.append(ev)
if isinstance(ev, NoteStartEvent):
events_json.append({"type": "start", "index": ev.index, "pitch": ev.pitch,
"time": round(ev.start_time, 6), "instrument": ev.instrument})
elif isinstance(ev, NoteEndEvent):
events_json.append({"type": "end", "index": ev.start_event_index,
"time": round(ev.end_time, 6)})
(out / "events_ref15.json").write_text(json.dumps(events_json))
midi_bytes = tm.events_to_midi_bytes(iter(collected))
(out / "ref15.mid").write_bytes(midi_bytes)
top = torch.topk(logits[0, -1], 5)
manifest = {
"dim": dim,
"card": int(model.card),
"bos_id": int(model.initial_token_id),
"eos_id": int(eos),
"prefix_len": int(prefix.shape[1]),
"wav_samples": int(wav.shape[0]),
"greedy_tokens": len(tokens),
"wav15_samples": int(wav15.shape[0]),
"events15": len(events_json),
"ref15_mid_bytes": len(midi_bytes),
"logits_top5_ids": top.indices.tolist(),
"logits_top5_vals": [round(v, 6) for v in top.values.tolist()],
}
(out / "manifest.json").write_text(json.dumps(manifest, indent=2))
print(json.dumps(manifest, indent=2))
if __name__ == "__main__":
main()
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+627
View File
@@ -0,0 +1,627 @@
// ace-synth.cpp: ACE-Step synthesis CLI
// Thin wrapper: parses args, scans the model registry, calls pipeline-synth,
// writes output files. Model selection (synth_model, adapter, output_format)
// comes from the request JSON. The registry resolves names to GGUF paths
// under --models <dir> and --adapters <dir>.
#include "audio-io.h"
#include "backend.h"
#include "ggml.h"
#include "lua-plugin-registry.h"
#include "model-registry.h"
#include "model-store.h"
#include "pipeline-synth.h"
#include "request.h"
#include "synth-batch-runner.h"
#include "task-types.h"
#include "version.h"
#include <cmath>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <filesystem>
#include <string>
#include <vector>
// ─── Per-section mask broadcast self-test (HOTSTEP_BCAST_TEST) ───────────────
// Verifies the exact tensor ops the per-section masking relies on, on the real
// backend, with no models. Test 1: ggml_mul([out,S,N] f32, [1,S,1] f32) — the
// per-frame mask broadcast over the feature (ne0) and batch (ne2) dims. Test 2:
// the real chain mul_mat(BF16 delta, f32 x) -> mul(mask). Run: set the env var
// and invoke ace-synth (it runs the test and exits).
static int run_bcast_selftest() {
BackendPair bp = backend_init("BcastTest");
ggml_backend_t backend = bp.backend;
fprintf(stderr, "[BcastTest] backend=%s\n", ggml_backend_name(backend));
const int64_t out = 8, S = 6, N = 2, in = 4;
int fails = 0;
// Test 1: pure elementwise broadcast mul.
{
struct ggml_init_params p = { ggml_tensor_overhead() * 16 + ggml_graph_overhead() + 4096, NULL, true };
struct ggml_context * ctx = ggml_init(p);
struct ggml_tensor * a = ggml_new_tensor_3d(ctx, GGML_TYPE_F32, out, S, N);
struct ggml_tensor * mask = ggml_new_tensor_3d(ctx, GGML_TYPE_F32, 1, S, 1);
ggml_set_input(a);
ggml_set_input(mask);
struct ggml_tensor * y = ggml_mul(ctx, a, mask);
struct ggml_cgraph * gf = ggml_new_graph(ctx);
ggml_build_forward_expand(gf, y);
ggml_backend_buffer_t buf = ggml_backend_alloc_ctx_tensors(ctx, backend);
std::vector<float> ad((size_t) (out * S * N), 1.0f), md((size_t) S);
for (int s = 0; s < S; s++) md[s] = (float) (s + 1) * 0.25f; // distinct per frame
ggml_backend_tensor_set(a, ad.data(), 0, ad.size() * sizeof(float));
ggml_backend_tensor_set(mask, md.data(), 0, md.size() * sizeof(float));
ggml_backend_graph_compute(backend, gf);
std::vector<float> yd((size_t) (out * S * N));
ggml_backend_tensor_get(y, yd.data(), 0, yd.size() * sizeof(float));
int bad = 0;
for (int n = 0; n < N; n++)
for (int s = 0; s < S; s++)
for (int f = 0; f < out; f++) {
float got = yd[(size_t) n * S * out + (size_t) s * out + f];
if (fabsf(got - md[s]) > 1e-4f) {
if (bad < 6) fprintf(stderr, "[BcastTest] T1 MISMATCH f=%d s=%d n=%d exp=%.3f got=%.3f\n", f, s, n, md[s], got);
bad++;
}
}
fprintf(stderr, "[BcastTest] T1 ggml_mul[out,S,N]x[1,S,1]: %s (%d/%lld bad)\n",
bad ? "FAIL" : "PASS", bad, (long long) (out * S * N));
fails += bad ? 1 : 0;
ggml_backend_buffer_free(buf);
ggml_free(ctx);
}
// Test 2: real chain — mul_mat(BF16 delta[in,out], f32 x[in,S,N]) then mask.
{
struct ggml_init_params p = { ggml_tensor_overhead() * 32 + ggml_graph_overhead() + 4096, NULL, true };
struct ggml_context * ctx = ggml_init(p);
struct ggml_tensor * d = ggml_new_tensor_2d(ctx, GGML_TYPE_BF16, in, out);
struct ggml_tensor * x = ggml_new_tensor_3d(ctx, GGML_TYPE_F32, in, S, N);
struct ggml_tensor * mask = ggml_new_tensor_3d(ctx, GGML_TYPE_F32, 1, S, 1);
ggml_set_input(d);
ggml_set_input(x);
ggml_set_input(mask);
struct ggml_tensor * dy = ggml_mul_mat(ctx, d, x); // [out,S,N]
struct ggml_tensor * dym = ggml_mul(ctx, dy, mask);
ggml_set_output(dy);
ggml_set_output(dym);
struct ggml_cgraph * gf = ggml_new_graph(ctx);
ggml_build_forward_expand(gf, dym);
ggml_build_forward_expand(gf, dy);
ggml_backend_buffer_t buf = ggml_backend_alloc_ctx_tensors(ctx, backend);
std::vector<ggml_bf16_t> dd((size_t) (in * out));
for (int i = 0; i < in * out; i++) dd[i] = ggml_fp32_to_bf16(0.5f); // all 0.5
std::vector<float> xd((size_t) (in * S * N), 1.0f), md((size_t) S);
for (int s = 0; s < S; s++) md[s] = (float) (s + 1) * 0.25f;
ggml_backend_tensor_set(d, dd.data(), 0, dd.size() * sizeof(ggml_bf16_t));
ggml_backend_tensor_set(x, xd.data(), 0, xd.size() * sizeof(float));
ggml_backend_tensor_set(mask, md.data(), 0, md.size() * sizeof(float));
ggml_backend_graph_compute(backend, gf);
std::vector<float> dyd((size_t) (out * S * N)), dymd((size_t) (out * S * N));
ggml_backend_tensor_get(dy, dyd.data(), 0, dyd.size() * sizeof(float));
ggml_backend_tensor_get(dym, dymd.data(), 0, dymd.size() * sizeof(float));
// Each dy element = sum_in(0.5 * 1.0) = in*0.5 = 2.0; masked = 2.0*md[s].
int bad = 0;
for (int n = 0; n < N; n++)
for (int s = 0; s < S; s++)
for (int f = 0; f < out; f++) {
size_t idx = (size_t) n * S * out + (size_t) s * out + f;
float exp = dyd[idx] * md[s];
if (fabsf(dymd[idx] - exp) > 1e-3f) {
if (bad < 6) fprintf(stderr, "[BcastTest] T2 MISMATCH f=%d s=%d n=%d dy=%.3f exp=%.3f got=%.3f\n", f, s, n, dyd[idx], exp, dymd[idx]);
bad++;
}
}
fprintf(stderr, "[BcastTest] T2 dy=%.3f (expect 2.0); mask chain: %s (%d/%lld bad)\n",
dyd[0], bad ? "FAIL" : "PASS", bad, (long long) (out * S * N));
fails += bad ? 1 : 0;
ggml_backend_buffer_free(buf);
ggml_free(ctx);
}
fprintf(stderr, "[BcastTest] RESULT: %s\n", fails ? "FAIL — broadcast is the bug" : "PASS — broadcast is fine, look elsewhere");
backend_release(bp.backend, bp.cpu_backend);
return fails ? 1 : 0;
}
// ─── LoKr Kronecker-apply self-test (HOTSTEP_KRON_TEST) ──────────────────────
// Phase-2 prototype for low-rank runtime adapters (docs/plans/lowrank-runtime-
// adapters.md): verifies that (w1 ⊗ w2)@x can be computed on the real backend
// from the factors alone — no materialized Kronecker delta — via
// y[oa·c+oc, s] = Σ_ib w1[oa,ib] · ( Σ_id w2[oc,id] · x[ib·d+id, s] )
// i.e. mul_mat(w2) → permute/cont → mul_mat(w1) → permute/cont. Pass 0 runs
// F32 factors (validates the choreography exactly), pass 1 runs BF16 factors
// (the production storage type). Compared against a host-side dense-kron
// reference. Batched inputs [in,S,N] flatten to [in,S·N] first, so 2D covers
// them. Run: set the env var and invoke ace-synth (runs the test and exits).
static int run_kron_selftest() {
BackendPair bp = backend_init("KronTest");
ggml_backend_t backend = bp.backend;
fprintf(stderr, "[KronTest] backend=%s\n", ggml_backend_name(backend));
// PyTorch shapes: w1 [a,b], w2 [c,d]; delta = kron(w1,w2) [out=a·c, in=b·d]
const int64_t a = 3, b = 4, c = 5, d = 6, S = 7;
const int64_t out = a * c, in = b * d;
int fails = 0;
// deterministic fill (LCG) — same values every run and backend
auto fill = [](std::vector<float> & v, uint32_t seed) {
uint32_t s = seed;
for (auto & f : v) {
s = s * 1664525u + 1013904223u;
f = ((float) (s >> 8) / (float) (1u << 24)) - 0.5f; // [-0.5, 0.5)
}
};
std::vector<float> w1d((size_t) (a * b)), w2d((size_t) (c * d)), xd((size_t) (in * S));
fill(w1d, 1);
fill(w2d, 2);
fill(xd, 3);
// Host reference: dense kron, then y = kron(w1,w2) @ x.
// w1d/w2d are row-major PyTorch [out, in]: w1[oa,ib] = w1d[oa·b+ib].
std::vector<float> yref((size_t) (out * S), 0.0f);
for (int64_t oa = 0; oa < a; oa++)
for (int64_t oc = 0; oc < c; oc++)
for (int64_t s = 0; s < S; s++) {
float acc = 0.0f;
for (int64_t ib = 0; ib < b; ib++)
for (int64_t id = 0; id < d; id++)
acc += w1d[(size_t) (oa * b + ib)] * w2d[(size_t) (oc * d + id)]
* xd[(size_t) ((ib * d + id) + in * s)];
yref[(size_t) ((oa * c + oc) + out * s)] = acc;
}
for (int pass = 0; pass < 2; pass++) {
const bool bf16 = (pass == 1);
const ggml_type ftype = bf16 ? GGML_TYPE_BF16 : GGML_TYPE_F32;
// F32 tol allows CUDA's TF32-accumulated cuBLAS matmul (~1e-3 rel), which
// reorders/rounds vs the naive host reference; a wrong permute would be
// off by whole values on most elements, not 1e-4 on a few.
const float tol = bf16 ? 2e-2f : 1e-3f;
struct ggml_init_params p = { ggml_tensor_overhead() * 32 + ggml_graph_overhead() + 4096, NULL, true };
struct ggml_context * ctx = ggml_init(p);
// Row-major PyTorch [rows, cols] uploads directly as ggml [cols, rows]:
// w1g [b, a] element (ib, oa) == w1[oa, ib]; same for w2g [d, c].
struct ggml_tensor * w1g = ggml_new_tensor_2d(ctx, ftype, b, a);
struct ggml_tensor * w2g = ggml_new_tensor_2d(ctx, ftype, d, c);
struct ggml_tensor * x = ggml_new_tensor_2d(ctx, GGML_TYPE_F32, in, S);
ggml_set_input(w1g);
ggml_set_input(w2g);
ggml_set_input(x);
// x [in=b·d, S] viewed as [d, b·S]: column ib·d+id has id fastest — matches
// the kron column convention, so the reshape is a free view.
struct ggml_tensor * X2 = ggml_reshape_2d(ctx, x, d, b * S);
struct ggml_tensor * T = ggml_mul_mat(ctx, w2g, X2); // [c, b·S] = T(oc; ib,s)
struct ggml_tensor * T3 = ggml_reshape_3d(ctx, T, c, b, S);
struct ggml_tensor * P = ggml_cont(ctx, ggml_permute(ctx, T3, 1, 0, 2, 3)); // [b, c, S]
struct ggml_tensor * P2 = ggml_reshape_2d(ctx, P, b, c * S);
struct ggml_tensor * Y = ggml_mul_mat(ctx, w1g, P2); // [a, c·S] = y(oa; oc,s)
struct ggml_tensor * Y3 = ggml_reshape_3d(ctx, Y, a, c, S);
struct ggml_tensor * YP = ggml_cont(ctx, ggml_permute(ctx, Y3, 1, 0, 2, 3)); // [c, a, S] → flat out=oa·c+oc
ggml_set_output(YP);
struct ggml_cgraph * gf = ggml_new_graph(ctx);
ggml_build_forward_expand(gf, YP);
ggml_backend_buffer_t buf = ggml_backend_alloc_ctx_tensors(ctx, backend);
if (bf16) {
std::vector<ggml_bf16_t> w1b((size_t) (a * b)), w2b((size_t) (c * d));
ggml_fp32_to_bf16_row(w1d.data(), w1b.data(), a * b);
ggml_fp32_to_bf16_row(w2d.data(), w2b.data(), c * d);
ggml_backend_tensor_set(w1g, w1b.data(), 0, w1b.size() * sizeof(ggml_bf16_t));
ggml_backend_tensor_set(w2g, w2b.data(), 0, w2b.size() * sizeof(ggml_bf16_t));
} else {
ggml_backend_tensor_set(w1g, w1d.data(), 0, w1d.size() * sizeof(float));
ggml_backend_tensor_set(w2g, w2d.data(), 0, w2d.size() * sizeof(float));
}
ggml_backend_tensor_set(x, xd.data(), 0, xd.size() * sizeof(float));
ggml_backend_graph_compute(backend, gf);
std::vector<float> yd((size_t) (out * S));
ggml_backend_tensor_get(YP, yd.data(), 0, yd.size() * sizeof(float));
int bad = 0;
float max_err = 0.0f;
for (int64_t o = 0; o < out; o++)
for (int64_t s = 0; s < S; s++) {
float exp = yref[(size_t) (o + out * s)];
float got = yd[(size_t) (o + out * s)];
float err = fabsf(got - exp);
if (err > max_err) max_err = err;
if (err > tol) {
if (bad < 6) fprintf(stderr, "[KronTest] %s MISMATCH o=%lld s=%lld exp=%.5f got=%.5f\n",
bf16 ? "BF16" : "F32", (long long) o, (long long) s, exp, got);
bad++;
}
}
fprintf(stderr, "[KronTest] %s factors: %s (%d/%lld bad, max_err=%.3g, tol=%.3g)\n",
bf16 ? "BF16" : "F32", bad ? "FAIL" : "PASS", bad, (long long) (out * S), max_err, tol);
fails += bad ? 1 : 0;
ggml_backend_buffer_free(buf);
ggml_free(ctx);
}
fprintf(stderr, "[KronTest] RESULT: %s\n",
fails ? "FAIL — Kronecker apply choreography is wrong" : "PASS — LoKr factor apply is viable on this backend");
backend_release(bp.backend, bp.cpu_backend);
return fails ? 1 : 0;
}
static void usage(const char * prog) {
AceSynthParams d;
ace_synth_default_params(&d);
fprintf(stderr, "acestep.cpp %s\n\n", ACE_VERSION);
fprintf(stderr,
"Usage: %s --models <dir> --request <json...> [options]\n\n"
"Required:\n"
" --models <dir> Directory of GGUF model files\n"
" --request <json...> One or more request JSONs (from ace-lm --request)\n\n"
"Optional:\n"
" --adapters <dir> Directory of adapter files (enables JSON adapter field)\n"
" --src-audio <file> Source audio (WAV or MP3)\n"
" --ref-audio <file> Timbre reference audio (WAV or MP3)\n\n"
"Model selection comes from the request JSON: synth_model picks the DiT,\n"
"adapter picks an adapter from --adapters, output_format picks the output\n"
"extension. When synth_model is empty the first DiT in the registry is used;\n"
"text-encoder and VAE are always the first in their registry bucket.\n\n"
"Audio encoding:\n"
" --mp3-bitrate <kbps> MP3 bitrate (default: 128)\n\n"
"Memory control:\n"
" --vae-chunk <N> Latent frames per tile (default: %d)\n"
" --vae-overlap <N> Overlap frames per side (default: %d)\n\n"
"Debug:\n"
" --no-fa Disable flash attention\n"
" --no-batch-cfg Split DiT CFG into two separate forwards\n"
" --clamp-fp16 Clamp hidden states to FP16 range\n"
" --dump <dir> Dump intermediate tensors\n",
prog, d.vae_chunk, d.vae_overlap);
}
int main(int argc, char ** argv) {
if (std::getenv("HOTSTEP_BCAST_TEST")) {
return run_bcast_selftest();
}
if (std::getenv("HOTSTEP_KRON_TEST")) {
return run_kron_selftest();
}
if (argc < 2) {
usage(argv[0]);
return 1;
}
// Load solver/scheduler/guidance Lua plugins. The fork routes ALL
// sampling through hot-step-sampler.h, which resolves solvers from the
// plugin registry — without this init the standalone CLI had no solvers
// at all ("unknown solver 'euler'") and crashed in the fallback path.
// Same exe-relative resolution as hot-step-server.cpp.
{
std::filesystem::path exe_path = std::filesystem::canonical(argv[0]);
std::filesystem::path exe_dir = exe_path.parent_path();
std::string dir_name = exe_dir.filename().string();
std::filesystem::path engine_dir;
if (dir_name == "Release" || dir_name == "Debug" ||
dir_name == "RelWithDebInfo" || dir_name == "MinSizeRel") {
engine_dir = exe_dir.parent_path().parent_path();
} else if (dir_name == "build") {
engine_dir = exe_dir.parent_path();
} else {
engine_dir = exe_dir;
}
std::filesystem::path project_dir = engine_dir.parent_path();
PluginRegistry::instance().init(engine_dir.string(), project_dir.string());
}
// Defaults live in ace_synth_default_params. CLI locals read from params
// so there is exactly one place in the codebase that picks the numbers.
AceSynthParams params;
ace_synth_default_params(&params);
std::vector<const char *> request_paths;
const char * models_dir = NULL;
const char * adapters_dir = NULL;
const char * src_audio_path = NULL;
const char * ref_audio_path = NULL;
const char * dump_dir = NULL;
bool use_fa = true;
bool use_batch_cfg = true;
bool clamp_fp16 = false;
int vae_chunk = params.vae_chunk;
int vae_overlap = params.vae_overlap;
int mp3_kbps = 128;
for (int i = 1; i < argc; i++) {
if (!strcmp(argv[i], "--request")) {
// 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], "--models") && i + 1 < argc) {
models_dir = argv[++i];
} else if (!strcmp(argv[i], "--adapters") && i + 1 < argc) {
adapters_dir = argv[++i];
} else if (!strcmp(argv[i], "--src-audio") && i + 1 < argc) {
src_audio_path = argv[++i];
} else if (!strcmp(argv[i], "--ref-audio") && i + 1 < argc) {
ref_audio_path = argv[++i];
} else if (!strcmp(argv[i], "--dump") && i + 1 < argc) {
dump_dir = argv[++i];
} else if (!strcmp(argv[i], "--no-fa")) {
use_fa = false;
} else if (!strcmp(argv[i], "--no-batch-cfg")) {
use_batch_cfg = false;
} else if (!strcmp(argv[i], "--clamp-fp16")) {
clamp_fp16 = true;
} else if (!strcmp(argv[i], "--vae-chunk") && i + 1 < argc) {
vae_chunk = atoi(argv[++i]);
} else if (!strcmp(argv[i], "--vae-overlap") && i + 1 < argc) {
vae_overlap = atoi(argv[++i]);
} else if (!strcmp(argv[i], "--mp3-bitrate") && i + 1 < argc) {
mp3_kbps = atoi(argv[++i]);
} else if (!strcmp(argv[i], "--help") || !strcmp(argv[i], "-h")) {
usage(argv[0]);
return 0;
} else {
fprintf(stderr, "Unknown option: %s\n", argv[i]);
usage(argv[0]);
return 1;
}
}
if (!models_dir) {
fprintf(stderr, "[CLI] ERROR: --models required\n");
usage(argv[0]);
return 1;
}
if (request_paths.empty()) {
fprintf(stderr, "[CLI] ERROR: --request required\n");
usage(argv[0]);
return 1;
}
// Parse all requests first: the first request drives model selection.
int batch_n = (int) request_paths.size();
std::vector<AceRequest> reqs(batch_n);
std::vector<std::string> basenames(batch_n);
for (int ri = 0; ri < batch_n; ri++) {
const char * rpath = request_paths[ri];
if (!request_parse(&reqs[ri], rpath)) {
fprintf(stderr, "[Ace-Synth] FATAL: failed to parse %s\n", rpath);
return 1;
}
request_dump(&reqs[ri], stderr);
if (reqs[ri].caption.empty() && reqs[ri].task_type != TASK_LEGO && reqs[ri].task_type != TASK_EXTRACT &&
reqs[ri].task_type != TASK_COMPLETE) {
fprintf(stderr, "[Ace-Synth] FATAL: caption is empty in %s\n", rpath);
return 1;
}
// output basename: strip .json suffix
basenames[ri] = rpath;
size_t dot = basenames[ri].rfind(".json");
if (dot != std::string::npos) {
basenames[ri] = basenames[ri].substr(0, dot);
}
}
fprintf(stderr, "[Ace-Synth] Batch: %d request(s)\n", batch_n);
// Scan the registry and resolve model paths from the first request.
ModelRegistry registry;
if (!registry_scan(&registry, models_dir)) {
fprintf(stderr, "[Ace-Synth] FATAL: cannot scan --models %s\n", models_dir);
return 1;
}
if (adapters_dir) {
registry_scan_adapters(&registry, adapters_dir);
}
if (registry.dit.empty() || registry.text_enc.empty() || registry.vae.empty()) {
fprintf(stderr, "[Ace-Synth] FATAL: registry needs DiT, text-encoder and VAE models\n");
return 1;
}
const ModelEntry * dit_entry =
reqs[0].synth_model.empty() ? &registry.dit[0] : registry_find(registry.dit, reqs[0].synth_model.c_str());
if (!dit_entry) {
fprintf(stderr, "[Ace-Synth] FATAL: synth_model '%s' not found in registry\n", reqs[0].synth_model.c_str());
return 1;
}
const AdapterEntry * adapter_entry = NULL;
if (!reqs[0].adapter.empty()) {
adapter_entry = registry_find_adapter(registry, reqs[0].adapter.c_str());
if (!adapter_entry) {
fprintf(stderr, "[Ace-Synth] FATAL: adapter '%s' not found (use --adapters <dir>)\n",
reqs[0].adapter.c_str());
return 1;
}
}
// Multi-adapter stack: the `adapters` array supersedes the single `adapter`
// field. Fold the single field into a one-element stack so the load path is
// uniform. The resolved stack drives merge/runtime loading via the sideband.
g_hotstep_params.adapters.clear();
{
std::vector<AceAdapterRef> stack = reqs[0].adapters;
if (stack.empty() && adapter_entry) {
stack.push_back({ reqs[0].adapter, reqs[0].adapter_scale });
}
for (const auto & ar : stack) {
const AdapterEntry * e = registry_find_adapter(registry, ar.name.c_str());
std::string path;
if (e) {
path = e->path;
} else {
FILE * t = fopen(ar.name.c_str(), "rb");
if (t) { fclose(t); path = ar.name; }
}
if (path.empty()) {
fprintf(stderr, "[Ace-Synth] FATAL: adapter '%s' not found (use --adapters <dir>)\n",
ar.name.c_str());
return 1;
}
g_hotstep_params.adapters.push_back({ path, ar.scale });
}
}
// Resolve output_format to (is_mp3, wav_fmt).
bool is_mp3 = true;
WavFormat wav_fmt = WAV_S16;
if (!audio_parse_format(reqs[0].output_format.c_str(), is_mp3, wav_fmt)) {
fprintf(stderr, "[Ace-Synth] FATAL: invalid output_format '%s' (use: mp3, wav16, wav24, wav32)\n",
reqs[0].output_format.c_str());
return 1;
}
// Fill params from registry lookups and CLI flags.
params.text_encoder_path = registry.text_enc[0].path.c_str();
params.dit_path = dit_entry->path.c_str();
params.vae_path = registry.vae[0].path.c_str();
params.adapter_path = g_hotstep_params.adapters.empty() ? NULL
: g_hotstep_params.adapters[0].path.c_str();
params.adapter_scale = g_hotstep_params.adapters.empty() ? 1.0f
: g_hotstep_params.adapters[0].scale;
params.use_fa = use_fa;
params.use_batch_cfg = use_batch_cfg;
params.clamp_fp16 = clamp_fp16;
params.vae_chunk = vae_chunk;
params.vae_overlap = vae_overlap;
params.dump_dir = dump_dir;
// Local store with the default STRICT policy: at most one GPU module
// resident at a time for this one-shot CLI. No module sharing across runs,
// so EVICT_STRICT frees the DiT before the VAE loads, and so on.
ModelStore * store = store_create(EVICT_STRICT);
AceSynth * ctx = ace_synth_load(store, &params);
if (!ctx) {
store_free(store);
return 1;
}
// Read source audio (cover/lego mode)
float * src_interleaved = NULL;
int src_len = 0;
if (src_audio_path) {
int T_audio = 0;
float * planar = audio_read_48k(src_audio_path, &T_audio);
if (!planar) {
fprintf(stderr, "[Ace-Synth] FATAL: cannot read --src-audio %s\n", src_audio_path);
ace_synth_free(ctx);
store_free(store);
return 1;
}
fprintf(stderr, "[Ace-Synth] Source audio: %.2fs @ 48kHz\n", (float) T_audio / 48000.0f);
src_interleaved = audio_planar_to_interleaved(planar, T_audio);
free(planar);
src_len = T_audio;
}
// Read reference audio (timbre conditioning)
float * ref_interleaved = NULL;
int ref_len = 0;
if (ref_audio_path) {
int T_audio = 0;
float * planar = audio_read_48k(ref_audio_path, &T_audio);
if (!planar) {
fprintf(stderr, "[Ace-Synth] FATAL: cannot read --ref-audio %s\n", ref_audio_path);
free(src_interleaved);
ace_synth_free(ctx);
store_free(store);
return 1;
}
fprintf(stderr, "[Ace-Synth] Reference audio: %.2fs @ 48kHz\n", (float) T_audio / 48000.0f);
ref_interleaved = audio_planar_to_interleaved(planar, T_audio);
free(planar);
ref_len = T_audio;
}
// Generate every request in one DiT batch. synth_batch_size expands each
// request into per-seed variants in groups[0]. Total clamped to DiT max 9.
int total_alloc = 0;
for (int ri = 0; ri < batch_n; ri++) {
int sbs = reqs[ri].synth_batch_size;
total_alloc += sbs < 1 ? 1 : (sbs > 9 ? 9 : sbs);
}
if (total_alloc > 9) {
fprintf(stderr, "[Ace-Synth] Batch %d exceeds DiT max 9, clamping\n", total_alloc);
total_alloc = 9;
}
std::vector<AceAudio> all_audio(total_alloc);
std::vector<std::string> all_basenames(total_alloc);
std::vector<int> all_synth_indices(total_alloc);
std::vector<std::vector<AceRequest>> groups(1);
groups[0].reserve(total_alloc);
int off = 0;
for (int ri = 0; ri < batch_n && off < total_alloc; ri++) {
int sbs = reqs[ri].synth_batch_size;
if (sbs < 1) {
sbs = 1;
}
if (sbs > 9) {
sbs = 9;
}
if (off + sbs > total_alloc) {
sbs = total_alloc - off;
}
// resolve seed once per original request
request_resolve_seed(&reqs[ri]);
const long long base_seed = reqs[ri].seed;
for (int i = 0; i < sbs; i++) {
AceRequest r = reqs[ri];
r.seed = base_seed + i;
groups[0].push_back(r);
all_basenames[off + i] = basenames[ri];
all_synth_indices[off + i] = i;
}
off += sbs;
}
if (total_alloc > 1) {
fprintf(stderr, "[Ace-Synth] Batch: %d track(s) from %d request(s)\n", total_alloc, batch_n);
}
// Two-phase run: DiT resident for all groups, then VAE for all jobs.
const int rc = synth_batch_run(ctx, groups, src_interleaved, src_len,
nullptr, 0, // src_latents
ref_interleaved, ref_len,
nullptr, 0, // ref_latents
all_audio.data());
if (rc != 0) {
fprintf(stderr, "[Ace-Synth] ERROR: batch run failed\n");
for (auto & a : all_audio) {
ace_audio_free(&a);
}
free(src_interleaved);
free(ref_interleaved);
ace_synth_free(ctx);
store_free(store);
return 1;
}
// Write output files
for (int b = 0; b < (int) all_audio.size(); b++) {
if (!all_audio[b].samples) {
continue;
}
const char * ext = is_mp3 ? ".mp3" : ".wav";
char out_path[1024];
snprintf(out_path, sizeof(out_path), "%s%d%s", all_basenames[b].c_str(), all_synth_indices[b], ext);
if (!audio_write(out_path, all_audio[b].samples, all_audio[b].n_samples, 48000, mp3_kbps, wav_fmt)) {
fprintf(stderr, "[Ace-Synth Batch%d] FATAL: failed to write %s\n", b, out_path);
}
ace_audio_free(&all_audio[b]);
}
free(src_interleaved);
free(ref_interleaved);
ace_synth_free(ctx);
store_free(store);
fprintf(stderr, "[Ace-Synth] All done\n");
return 0;
}
File diff suppressed because it is too large Load Diff
+202
View File
@@ -0,0 +1,202 @@
// ace-understand.cpp: audio understanding CLI (thin wrapper)
//
// Audio -> VAE encode -> FSQ tokenize -> LM understand -> metadata + lyrics
//
// Output: request JSON with metadata + lyrics, reusable as ace-lm or ace-synth input.
#include "audio-io.h"
#include "model-registry.h"
#include "model-store.h"
#include "pipeline-understand.h"
#include "request.h"
#include "version.h"
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <string>
static void usage(const char * prog) {
AceUnderstandParams d;
ace_understand_default_params(&d);
fprintf(stderr, "acestep.cpp %s\n\n", ACE_VERSION);
fprintf(stderr,
"Usage: %s --models <dir> --src-audio <file> [--request <json>] [options]\n"
"\n"
"Required:\n"
" --models <dir> Directory of GGUF model files\n"
" --src-audio <file> Source audio (WAV or MP3, any sample rate)\n"
"\n"
"Optional:\n"
" --request <json> Request JSON carrying model selection and\n"
" sampling params (lm_model, synth_model,\n"
" lm_temperature, lm_top_p, lm_top_k)\n"
"\n"
"When no --request is given, understand defaults apply\n"
"(temperature 0.3, top_p disabled).\n"
"\n"
"Output:\n"
" -o <json> Output JSON (default: stdout summary)\n"
"\n"
"Memory control:\n"
" --vae-chunk <N> Latent frames per tile (default: %d)\n"
" --vae-overlap <N> Overlap frames per side (default: %d)\n"
"\n"
"Debug:\n"
" --max-seq <N> KV cache size (default: %d)\n"
" --no-fsm Disable FSM constrained decoding\n"
" --no-fa Disable flash attention\n"
" --dump <dir> Dump tok_latents + tok_codes (skip LM)\n",
prog, d.vae_chunk, d.vae_overlap, d.max_seq);
}
int main(int argc, char ** argv) {
const char * models_dir = NULL;
const char * src_audio_path = NULL;
const char * request_path = NULL;
const char * output_path = NULL;
const char * dump_dir = NULL;
AceUnderstandParams params;
ace_understand_default_params(&params);
if (argc < 2) {
usage(argv[0]);
return 1;
}
for (int i = 1; i < argc; i++) {
if (!strcmp(argv[i], "--models") && i + 1 < argc) {
models_dir = argv[++i];
} else if (!strcmp(argv[i], "--src-audio") && i + 1 < argc) {
src_audio_path = argv[++i];
} else if (!strcmp(argv[i], "--request") && i + 1 < argc) {
request_path = argv[++i];
} else if (!strcmp(argv[i], "-o") && i + 1 < argc) {
output_path = argv[++i];
} else if (!strcmp(argv[i], "--dump") && i + 1 < argc) {
dump_dir = argv[++i];
} else if (!strcmp(argv[i], "--max-seq") && i + 1 < argc) {
params.max_seq = atoi(argv[++i]);
} else if (!strcmp(argv[i], "--vae-chunk") && i + 1 < argc) {
params.vae_chunk = atoi(argv[++i]);
} else if (!strcmp(argv[i], "--vae-overlap") && i + 1 < argc) {
params.vae_overlap = atoi(argv[++i]);
} else if (!strcmp(argv[i], "--no-fsm")) {
params.use_fsm = false;
} else if (!strcmp(argv[i], "--no-fa")) {
params.use_fa = false;
} else if (!strcmp(argv[i], "--help") || !strcmp(argv[i], "-h")) {
usage(argv[0]);
return 0;
} else {
fprintf(stderr, "Unknown option: %s\n", argv[i]);
usage(argv[0]);
return 1;
}
}
if (!models_dir) {
fprintf(stderr, "[CLI] ERROR: --models required\n");
usage(argv[0]);
return 1;
}
if (!src_audio_path) {
fprintf(stderr, "[CLI] ERROR: --src-audio required\n");
usage(argv[0]);
return 1;
}
// Parse request JSON (if provided). Sampling params come from JSON.
// When no JSON, understand defaults apply (temperature=0.3 for transcription).
AceRequest req;
request_init(&req);
req.lm_temperature = 0.3f; // understand default: lower than generation
req.lm_top_p = 1.0f; // understand default: no nucleus sampling
if (request_path) {
if (!request_parse(&req, request_path)) {
return 1;
}
request_dump(&req, stderr);
}
// Scan the registry and resolve model paths. Empty lm_model / synth_model
// fall to the first entry of their bucket, matching server default behavior.
ModelRegistry registry;
if (!registry_scan(&registry, models_dir)) {
fprintf(stderr, "[Ace-Understand] FATAL: cannot scan --models %s\n", models_dir);
return 1;
}
if (registry.dit.empty() || registry.vae.empty()) {
fprintf(stderr, "[Ace-Understand] FATAL: understand pipeline needs DiT and VAE models under %s\n", models_dir);
return 1;
}
if (!dump_dir && registry.lm.empty()) {
fprintf(stderr, "[Ace-Understand] FATAL: understand pipeline needs an LM model under %s\n", models_dir);
return 1;
}
const ModelEntry * lm_entry =
dump_dir ? NULL : (req.lm_model.empty() ? &registry.lm[0] : registry_find(registry.lm, req.lm_model.c_str()));
if (!dump_dir && !lm_entry) {
fprintf(stderr, "[Ace-Understand] FATAL: lm_model '%s' not found in registry\n", req.lm_model.c_str());
return 1;
}
const ModelEntry * dit_entry =
req.synth_model.empty() ? &registry.dit[0] : registry_find(registry.dit, req.synth_model.c_str());
if (!dit_entry) {
fprintf(stderr, "[Ace-Understand] FATAL: synth_model '%s' not found in registry\n", req.synth_model.c_str());
return 1;
}
params.model_path = lm_entry ? lm_entry->path.c_str() : NULL;
params.dit_path = dit_entry->path.c_str();
params.vae_path = registry.vae[0].path.c_str();
params.dump_dir = dump_dir;
// load pipeline
ModelStore * store = store_create(EVICT_STRICT);
AceUnderstand * ctx = ace_understand_load(store, &params);
if (!ctx) {
store_free(store);
return 1;
}
// Read and resample audio to 48kHz stereo
int T_audio = 0;
float * planar = audio_read_48k(src_audio_path, &T_audio);
if (!planar) {
fprintf(stderr, "[Ace-Understand] FATAL: cannot read %s\n", src_audio_path);
ace_understand_free(ctx);
store_free(store);
return 1;
}
fprintf(stderr, "[Ace-Understand] %.2fs @ 48kHz\n", (float) T_audio / 48000.0f);
float * src_interleaved = audio_planar_to_interleaved(planar, T_audio);
free(planar);
int src_len = T_audio;
// run understand pipeline
AceRequest out;
int rc = ace_understand_generate(ctx, src_interleaved, src_len,
nullptr, 0, // src_latents (audio path)
&req, &out,
nullptr, nullptr, // latent_out, T_latent_out
NULL, NULL);
free(src_interleaved);
ace_understand_free(ctx);
store_free(store);
if (rc != 0) {
return 1;
}
// write output JSON
if (output_path) {
request_write(&out, output_path);
}
return 0;
}
+216
View File
@@ -0,0 +1,216 @@
// bs-roformer-test.cpp: numeric validation of bs-roformer-ggml.h.
//
// Compares the GGML graph against PyTorch reference activations dumped by
// scripts/dump_bs_roformer_goldens.py, stage by stage, so a divergence can be
// localised to a layer instead of only showing up as bad audio.
//
// The goldens .npz is converted to a flat .bin by scripts/goldens_to_bin.py
// (npz parsing in C++ is not worth the code).
//
// Usage:
// bs-roformer-test <model.gguf> <goldens.bin> [--no-flash]
//
// Part of HOT-Step CPP. MIT license.
#include <cmath>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <string>
#include <vector>
#include "bs-roformer-ggml.h"
namespace {
// Flat golden file layout (little-endian):
// magic "BSRG" 4 bytes
// int32 T, in_dim, depth, dim, n_bands, n_stems
// f32 input [in_dim * T]
// f32 band_split [dim * n_bands * T]
// f32 layer_00 [dim * n_bands * T]
// f32 layer_01 [dim * n_bands * T]
// f32 layer_last [dim * n_bands * T]
// f32 final_norm [dim * n_bands * T]
// f32 mask [n_stems * in_dim * T]
struct Goldens {
int32_t T = 0, in_dim = 0, depth = 0, dim = 0, n_bands = 0, n_stems = 0;
std::vector<float> input, band_split, layer_00, layer_01, layer_last, final_norm, mask;
};
bool read_block(FILE * f, std::vector<float> & v, size_t n) {
v.resize(n);
return fread(v.data(), sizeof(float), n, f) == n;
}
bool load_goldens(const char * path, Goldens & g) {
FILE * f = fopen(path, "rb");
if (!f) {
fprintf(stderr, "cannot open %s\n", path);
return false;
}
char magic[4];
if (fread(magic, 1, 4, f) != 4 || memcmp(magic, "BSRG", 4) != 0) {
fprintf(stderr, "%s: bad magic\n", path);
fclose(f);
return false;
}
int32_t hdr[6];
if (fread(hdr, sizeof(int32_t), 6, f) != 6) {
fclose(f);
return false;
}
g.T = hdr[0]; g.in_dim = hdr[1]; g.depth = hdr[2];
g.dim = hdr[3]; g.n_bands = hdr[4]; g.n_stems = hdr[5];
const size_t hidden = (size_t) g.dim * g.n_bands * g.T;
bool ok = read_block(f, g.input, (size_t) g.in_dim * g.T)
&& read_block(f, g.band_split, hidden)
&& read_block(f, g.layer_00, hidden)
&& read_block(f, g.layer_01, hidden)
&& read_block(f, g.layer_last, hidden)
&& read_block(f, g.final_norm, hidden)
&& read_block(f, g.mask, (size_t) g.n_stems * g.in_dim * g.T);
fclose(f);
if (!ok) fprintf(stderr, "%s: truncated\n", path);
return ok;
}
struct Cmp {
double max_abs = 0.0, mean_abs = 0.0, ref_peak = 0.0, got_peak = 0.0, rel = 0.0;
size_t worst_idx = 0;
bool finite = true;
};
Cmp compare(const std::vector<float> & got, const std::vector<float> & ref) {
Cmp c;
const size_t n = ref.size() < got.size() ? ref.size() : got.size();
double sum = 0.0;
for (size_t i = 0; i < n; i++) {
if (!std::isfinite(got[i])) c.finite = false;
double d = std::fabs((double) got[i] - (double) ref[i]);
if (d > c.max_abs) { c.max_abs = d; c.worst_idx = i; }
sum += d;
double a = std::fabs((double) ref[i]);
if (a > c.ref_peak) c.ref_peak = a;
double b = std::fabs((double) got[i]);
if (b > c.got_peak) c.got_peak = b;
}
c.mean_abs = n ? sum / (double) n : 0.0;
c.rel = c.ref_peak > 0.0 ? c.max_abs / c.ref_peak : c.max_abs;
return c;
}
// Tolerances are per-stage and deliberately loose at the tail.
//
// F32 GGML vs F32 PyTorch across 16 layers of a residual stream accumulates
// genuine reordering drift, and it is much worse for the instrumental
// checkpoint than the vocal one: its activations reach absmax ~321 (vs ~32),
// and final_norm then divides that down by ~90, so a ~1% relative difference
// at layer_last lands intact on the mask.
//
// That is conditioning, not a bug — the same graph on the vocal weights
// matches to 2e-3, and the flash and materialised-F32 paths agree with each
// other to better than they each agree with PyTorch. The checkpoints were
// trained with use_amp=true, so they are not precision-critical at 1e-3.
// A 1% mask error is ~0.09 dB of amplitude — well below audibility.
bool report(const char * label, const Cmp & c, double tol) {
const bool pass = c.finite && c.rel <= tol;
printf(" %-12s max %.3e mean %.3e ref_pk %.3e got_pk %.3e rel %.2e %s\n",
label, c.max_abs, c.mean_abs, c.ref_peak, c.got_peak, c.rel,
!c.finite ? "NON-FINITE" : (pass ? "ok" : "FAIL"));
return pass;
}
} // namespace
int main(int argc, char ** argv) {
if (argc < 3) {
fprintf(stderr,
"usage: %s <model.gguf> <goldens.bin> [--no-flash]\n", argv[0]);
return 2;
}
const char * gguf_path = argv[1];
const char * gold_path = argv[2];
bool no_flash = false;
for (int i = 3; i < argc; i++) {
if (strcmp(argv[i], "--no-flash") == 0) no_flash = true;
}
Goldens g;
if (!load_goldens(gold_path, g)) return 1;
printf("goldens: T=%d in_dim=%d depth=%d dim=%d bands=%d stems=%d\n",
g.T, g.in_dim, g.depth, g.dim, g.n_bands, g.n_stems);
BsRoformer m;
if (!bsr_load(&m, gguf_path)) return 1;
m.use_flash_attn = !no_flash;
printf("attention: %s\n\n", m.use_flash_attn ? "flash" : "materialised f32");
if (m.cfg.in_dim != g.in_dim || m.cfg.depth != g.depth ||
m.cfg.dim != g.dim || m.cfg.n_bands != g.n_bands) {
fprintf(stderr, "model/goldens mismatch\n");
return 1;
}
const size_t hidden = (size_t) g.dim * g.n_bands * g.T;
std::vector<float> out((size_t) g.n_stems * g.in_dim * g.T);
bool all_ok = true;
struct Stage {
int stage;
const char * label;
const std::vector<float> * ref;
double tol;
};
const Stage stages[] = {
{ 0, "band_split", &g.band_split, 2e-3 },
{ 1, "layer_00", &g.layer_00, 5e-3 },
{ 2, "layer_01", &g.layer_01, 5e-3 },
{ g.depth, "layer_last", &g.layer_last, 2e-2 },
{ g.depth + 1, "final_norm", &g.final_norm, 2e-2 },
};
for (const Stage & s : stages) {
m.debug_stage = s.stage;
bsr_forward(&m, g.input.data(), g.T, out.data());
if (m.debug_out.size() != hidden) {
fprintf(stderr, " %-12s debug buffer %zu != %zu\n",
s.label, m.debug_out.size(), hidden);
all_ok = false;
continue;
}
all_ok &= report(s.label, compare(m.debug_out, *s.ref), s.tol);
}
m.debug_stage = -1;
bsr_forward(&m, g.input.data(), g.T, out.data());
// 8e-2 accommodates the instrumental checkpoint's conditioning (see above);
// the vocal one lands at ~2e-3 against the same bar.
all_ok &= report("mask", compare(out, g.mask), 8e-2);
bsr_free(&m);
// Regression guard: load/free/load again in the same process. The engine
// runs the vocal then the instrumental checkpoint back to back, and
// backend_init hands out a refcounted shared singleton — a bsr_free that
// frees it outright leaves the second load holding a dangling backend and
// segfaults. A single-model test cannot see that, so do it explicitly.
{
printf("\nreload check (shared-backend refcount)...\n");
BsRoformer m2;
if (!bsr_load(&m2, gguf_path)) {
fprintf(stderr, " reload FAILED — backend teardown is wrong\n");
return 1;
}
std::vector<float> out2((size_t) g.n_stems * g.in_dim * g.T);
m2.debug_stage = -1;
bsr_forward(&m2, g.input.data(), g.T, out2.data());
all_ok &= report("mask(reload)", compare(out2, g.mask), 8e-2);
bsr_free(&m2);
}
printf("\n%s\n", all_ok ? "PASS — GGML matches the PyTorch reference"
: "FAIL — see stages above");
return all_ok ? 0 : 1;
}
File diff suppressed because it is too large Load Diff
+401
View File
@@ -0,0 +1,401 @@
// mastering.cpp — Reference-based audio mastering CLI tool
//
// Usage: mastering --target input.wav --reference ref.wav --output mastered.wav
// [--no-limiter] [--pcm24]
//
// Implements the matchering algorithm: spectral + RMS matching against a reference track.
#include <chrono>
#include <cmath>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <string>
#include <vector>
#include "mastering.h"
// ─── Minimal WAV Reader/Writer ──────────────────────────────────────
#pragma pack(push, 1)
struct WavHeader {
char riff[4]; // "RIFF"
uint32_t file_size; // file size - 8
char wave[4]; // "WAVE"
};
struct WavChunkHdr {
char id[4];
uint32_t size;
};
struct WavFmt {
uint16_t format; // 1=PCM, 3=IEEE float
uint16_t channels;
uint32_t sample_rate;
uint32_t byte_rate;
uint16_t block_align;
uint16_t bits_per_sample;
};
#pragma pack(pop)
struct WavData {
std::vector<float> L, R;
int sample_rate;
int channels;
};
static bool wav_read(const char * path, WavData & out) {
FILE * f = fopen(path, "rb");
if (!f) {
fprintf(stderr, "[WAV] Cannot open: %s\n", path);
return false;
}
WavHeader hdr;
if (fread(&hdr, sizeof(hdr), 1, f) != 1
|| memcmp(hdr.riff, "RIFF", 4) != 0
|| memcmp(hdr.wave, "WAVE", 4) != 0) {
fprintf(stderr, "[WAV] Invalid WAV header: %s\n", path);
fclose(f);
return false;
}
WavFmt fmt = {};
bool found_fmt = false, found_data = false;
int data_size = 0;
while (!feof(f)) {
WavChunkHdr chunk;
if (fread(&chunk, sizeof(chunk), 1, f) != 1) break;
if (memcmp(chunk.id, "fmt ", 4) == 0) {
int to_read = std::min((uint32_t) sizeof(fmt), chunk.size);
if (fread(&fmt, to_read, 1, f) != 1) break;
// Skip extra fmt bytes
if (chunk.size > (uint32_t) to_read) {
fseek(f, chunk.size - to_read, SEEK_CUR);
}
found_fmt = true;
} else if (memcmp(chunk.id, "data", 4) == 0) {
data_size = chunk.size;
found_data = true;
break; // data follows
} else {
// Skip unknown chunk
fseek(f, chunk.size, SEEK_CUR);
}
}
if (!found_fmt || !found_data) {
fprintf(stderr, "[WAV] Missing fmt or data chunk: %s\n", path);
fclose(f);
return false;
}
// WAVE_FORMAT_EXTENSIBLE (0xFFFE): the real format code is in the sub-format
// GUID at the end of the extended fmt chunk. The first 2 bytes of the GUID
// encode the actual format (1=PCM, 3=float). Common for 24/32-bit and
// multi-channel audio from DAWs.
if (fmt.format == 0xFFFE) {
// The extended fmt chunk has: cbSize(2) + validBitsPerSample(2) +
// channelMask(4) + subFormat GUID(16). We already read the base WavFmt
// (16 bytes), and we skipped any extra bytes. We need to re-read the
// extension. Seek back to re-read the extension portion.
// Actually, we skipped extra fmt bytes — we need to read them before skip.
// Let's fix: the fmt chunk was already fully consumed (read + skip).
// We need to handle this in the fmt reading section above. For now,
// the simplest fix: re-open and re-parse just the fmt extension.
fprintf(stderr, "[WAV] WAVE_FORMAT_EXTENSIBLE detected, attempting re-parse: %s\n", path);
fclose(f);
f = fopen(path, "rb");
if (!f) return false;
fseek(f, sizeof(WavHeader), SEEK_SET);
// Scan for fmt chunk again
while (!feof(f)) {
WavChunkHdr chunk2;
if (fread(&chunk2, sizeof(chunk2), 1, f) != 1) break;
if (memcmp(chunk2.id, "fmt ", 4) == 0) {
// Read base fmt (16 bytes)
WavFmt fmt2 = {};
int base = std::min((uint32_t)sizeof(fmt2), chunk2.size);
if (fread(&fmt2, base, 1, f) != 1) break;
// Read extension: cbSize(2), validBits(2), channelMask(4), subFormat(16)
if (chunk2.size >= 40) { // 16 base + 2 cbSize + 2 validBits + 4 mask + 16 GUID
uint16_t cb_size = 0;
uint16_t valid_bits = 0;
uint32_t channel_mask = 0;
uint16_t sub_format = 0;
fread(&cb_size, 2, 1, f);
fread(&valid_bits, 2, 1, f);
fread(&channel_mask, 4, 1, f);
fread(&sub_format, 2, 1, f); // first 2 bytes of GUID = real format
fmt.format = sub_format;
if (valid_bits > 0) {
fmt.bits_per_sample = valid_bits;
}
fprintf(stderr, "[WAV] EXTENSIBLE sub-format: %d (%s), valid bits: %d\n",
sub_format, sub_format == 1 ? "PCM" : sub_format == 3 ? "float" : "unknown",
valid_bits > 0 ? valid_bits : fmt.bits_per_sample);
}
break;
} else {
fseek(f, chunk2.size, SEEK_CUR);
}
}
fclose(f);
// Re-open and seek to data chunk
f = fopen(path, "rb");
if (!f) return false;
fseek(f, sizeof(WavHeader), SEEK_SET);
found_data = false;
while (!feof(f)) {
WavChunkHdr chunk2;
if (fread(&chunk2, sizeof(chunk2), 1, f) != 1) break;
if (memcmp(chunk2.id, "data", 4) == 0) {
data_size = chunk2.size;
found_data = true;
break;
} else {
fseek(f, chunk2.size, SEEK_CUR);
}
}
if (!found_data) {
fprintf(stderr, "[WAV] Cannot find data chunk on re-parse: %s\n", path);
fclose(f);
return false;
}
}
if (fmt.format != 1 && fmt.format != 3) {
fprintf(stderr, "[WAV] Unsupported format %d (need PCM=1 or float=3): %s\n",
fmt.format, path);
fclose(f);
return false;
}
if (fmt.channels < 1 || fmt.channels > 2) {
fprintf(stderr, "[WAV] Unsupported channel count %d: %s\n", fmt.channels, path);
fclose(f);
return false;
}
int bytes_per_sample = fmt.bits_per_sample / 8;
int n_samples = data_size / (bytes_per_sample * fmt.channels);
out.sample_rate = fmt.sample_rate;
out.channels = fmt.channels;
out.L.resize(n_samples);
out.R.resize(n_samples);
// Read raw data
std::vector<uint8_t> raw(data_size);
if (fread(raw.data(), 1, data_size, f) != (size_t) data_size) {
fprintf(stderr, "[WAV] Truncated data: %s\n", path);
fclose(f);
return false;
}
fclose(f);
// Convert to float
for (int i = 0; i < n_samples; i++) {
for (int ch = 0; ch < fmt.channels; ch++) {
int offset = (i * fmt.channels + ch) * bytes_per_sample;
float val = 0.0f;
if (fmt.format == 3) {
// IEEE float
if (bytes_per_sample == 4) {
memcpy(&val, raw.data() + offset, 4);
} else if (bytes_per_sample == 8) {
double dval;
memcpy(&dval, raw.data() + offset, 8);
val = (float) dval;
}
} else {
// PCM integer
if (bytes_per_sample == 2) {
int16_t ival;
memcpy(&ival, raw.data() + offset, 2);
val = ival / 32768.0f;
} else if (bytes_per_sample == 3) {
int32_t ival = 0;
memcpy(&ival, raw.data() + offset, 3);
if (ival & 0x800000) ival |= 0xFF000000; // sign extend
val = ival / 8388608.0f;
} else if (bytes_per_sample == 4) {
int32_t ival;
memcpy(&ival, raw.data() + offset, 4);
val = (float) ((double) ival / 2147483648.0);
}
}
if (ch == 0) out.L[i] = val;
else out.R[i] = val;
}
}
// Mono → stereo
if (fmt.channels == 1) {
out.R = out.L;
out.channels = 2;
}
fprintf(stderr, "[WAV] Read %s: %d samples, %d ch, %d Hz, %d-bit %s\n",
path, n_samples, fmt.channels, fmt.sample_rate,
fmt.bits_per_sample, fmt.format == 3 ? "float" : "PCM");
return true;
}
static bool wav_write(const char * path, const float * L, const float * R, int n,
int sample_rate, int bits = 16) {
FILE * f = fopen(path, "wb");
if (!f) {
fprintf(stderr, "[WAV] Cannot create: %s\n", path);
return false;
}
int channels = 2;
int bytes_per_sample = bits / 8;
int data_size = n * channels * bytes_per_sample;
WavHeader hdr;
memcpy(hdr.riff, "RIFF", 4);
hdr.file_size = 36 + data_size;
memcpy(hdr.wave, "WAVE", 4);
WavChunkHdr fmt_chunk;
memcpy(fmt_chunk.id, "fmt ", 4);
fmt_chunk.size = 16;
WavFmt fmt;
fmt.format = (bits == 32) ? 3 : 1; // float or PCM
fmt.channels = channels;
fmt.sample_rate = sample_rate;
fmt.bits_per_sample = bits;
fmt.block_align = channels * bytes_per_sample;
fmt.byte_rate = sample_rate * fmt.block_align;
WavChunkHdr data_chunk;
memcpy(data_chunk.id, "data", 4);
data_chunk.size = data_size;
fwrite(&hdr, sizeof(hdr), 1, f);
fwrite(&fmt_chunk, sizeof(fmt_chunk), 1, f);
fwrite(&fmt, sizeof(fmt), 1, f);
fwrite(&data_chunk, sizeof(data_chunk), 1, f);
// Write interleaved samples
for (int i = 0; i < n; i++) {
float l = std::clamp(L[i], -1.0f, 1.0f);
float r = std::clamp(R[i], -1.0f, 1.0f);
if (bits == 16) {
int16_t sl = (int16_t) (l * 32767.0f);
int16_t sr = (int16_t) (r * 32767.0f);
fwrite(&sl, 2, 1, f);
fwrite(&sr, 2, 1, f);
} else if (bits == 24) {
int32_t sl = (int32_t) (l * 8388607.0f);
int32_t sr_v = (int32_t) (r * 8388607.0f);
fwrite(&sl, 3, 1, f);
fwrite(&sr_v, 3, 1, f);
} else if (bits == 32) {
fwrite(&l, 4, 1, f);
fwrite(&r, 4, 1, f);
}
}
fclose(f);
fprintf(stderr, "[WAV] Wrote %s: %d samples, %d Hz, %d-bit\n",
path, n, sample_rate, bits);
return true;
}
// ─── CLI ────────────────────────────────────────────────────────────
static void print_usage(const char * prog) {
fprintf(stderr,
"Usage: %s --target input.wav --reference ref.wav --output mastered.wav\n"
" [--pcm24] [--pcm32f]\n"
"\n"
"Reference-based audio mastering using the matchering algorithm.\n"
"Matches the RMS level, frequency spectrum, and dynamic range\n"
"of the target track to the reference track.\n"
"\n"
"Options:\n"
" --target PATH Input audio file to master (WAV)\n"
" --reference PATH Reference track to match against (WAV)\n"
" --output PATH Output mastered file (WAV)\n"
" --pcm24 Write 24-bit PCM output (default: 16-bit)\n"
" --pcm32f Write 32-bit float output\n",
prog);
}
int main(int argc, char ** argv) {
const char * target_path = nullptr;
const char * ref_path = nullptr;
const char * output_path = nullptr;
int output_bits = 16;
for (int i = 1; i < argc; i++) {
if (strcmp(argv[i], "--target") == 0 && i + 1 < argc) {
target_path = argv[++i];
} else if (strcmp(argv[i], "--reference") == 0 && i + 1 < argc) {
ref_path = argv[++i];
} else if (strcmp(argv[i], "--output") == 0 && i + 1 < argc) {
output_path = argv[++i];
} else if (strcmp(argv[i], "--pcm24") == 0) {
output_bits = 24;
} else if (strcmp(argv[i], "--pcm32f") == 0) {
output_bits = 32;
} 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 (!target_path || !ref_path || !output_path) {
fprintf(stderr, "Error: --target, --reference, and --output are required\n\n");
print_usage(argv[0]);
return 1;
}
auto t_start = std::chrono::high_resolution_clock::now();
// Read input files
WavData target, reference;
if (!wav_read(target_path, target)) return 1;
if (!wav_read(ref_path, reference)) return 1;
// Run mastering
auto result = mastering_process(
target.L.data(), target.R.data(), (int) target.L.size(), target.sample_rate,
reference.L.data(), reference.R.data(), (int) reference.L.size(), reference.sample_rate
);
if (!result.success) {
fprintf(stderr, "[Mastering] FAILED: %s\n", result.error ? result.error : "unknown");
return 1;
}
// Write output
if (!wav_write(output_path, result.L.data(), result.R.data(),
(int) result.L.size(), target.sample_rate, output_bits)) {
return 1;
}
auto t_end = std::chrono::high_resolution_clock::now();
double elapsed = std::chrono::duration<double>(t_end - t_start).count();
fprintf(stderr, "[Mastering] Total time: %.2f seconds\n", elapsed);
return 0;
}
+139
View File
@@ -0,0 +1,139 @@
// mdx23c-test.cpp: validation for mdx23c-ggml.h.
//
// Without a goldens file: loads the model, runs one forward at the trained
// frame count and checks the output shape, finiteness and range. That alone
// catches the shape/layout mistakes that are easy to make in a conv U-Net.
//
// With a goldens .bin (scripts/dump_mdx23c_goldens.py): also compares against
// the PyTorch reference elementwise.
//
// Usage:
// mdx23c-test <model.gguf> [goldens.bin]
//
// Part of HOT-Step CPP. MIT license.
#include <cmath>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <vector>
#include "mdx23c-ggml.h"
namespace {
// Flat golden layout: magic "MDXG", int32 T, dim_f, cin, n_inst,
// then f32 input [T*dim_f*cin], f32 output [n_inst*cin*dim_f*T].
struct Goldens {
int32_t T = 0, dim_f = 0, cin = 0, n_inst = 0;
std::vector<float> input, out;
};
bool load_goldens(const char * path, Goldens & g) {
FILE * f = fopen(path, "rb");
if (!f) { fprintf(stderr, "cannot open %s\n", path); return false; }
char magic[4];
int32_t hdr[4];
if (fread(magic, 1, 4, f) != 4 || memcmp(magic, "MDXG", 4) != 0 ||
fread(hdr, sizeof(int32_t), 4, f) != 4) {
fprintf(stderr, "%s: bad header\n", path); fclose(f); return false;
}
g.T = hdr[0]; g.dim_f = hdr[1]; g.cin = hdr[2]; g.n_inst = hdr[3];
g.input.resize((size_t) g.T * g.dim_f * g.cin);
g.out.resize((size_t) g.n_inst * g.cin * g.dim_f * g.T);
bool ok = fread(g.input.data(), sizeof(float), g.input.size(), f) == g.input.size()
&& fread(g.out.data(), sizeof(float), g.out.size(), f) == g.out.size();
fclose(f);
if (!ok) fprintf(stderr, "%s: truncated\n", path);
return ok;
}
} // namespace
int main(int argc, char ** argv) {
if (argc < 2) {
fprintf(stderr, "usage: %s <model.gguf> [goldens.bin]\n", argv[0]);
return 2;
}
Mdx23c m;
if (!mdx_load(&m, argv[1])) return 1;
const Mdx23cConfig & c = m.cfg;
const int cin = c.n_audio_channels * 2;
int T = c.chunk_size / c.hop_length + 1;
Goldens g;
const bool have_goldens = (argc >= 3) && load_goldens(argv[2], g);
if (argc >= 3 && !have_goldens) return 1;
if (have_goldens) {
if (g.dim_f != c.dim_f || g.cin != cin || g.n_inst != c.n_instruments) {
fprintf(stderr, "goldens/model mismatch\n");
return 1;
}
T = g.T;
}
printf("T=%d dim_f=%d cin=%d instruments=%d\n", T, c.dim_f, cin, c.n_instruments);
std::vector<float> in((size_t) T * c.dim_f * cin);
if (have_goldens) {
memcpy(in.data(), g.input.data(), in.size() * sizeof(float));
} else {
unsigned s = 1234;
for (size_t i = 0; i < in.size(); i++) {
s = s * 1664525u + 1013904223u;
in[i] = ((float) (s >> 8) / 8388608.0f - 1.0f) * 0.05f;
}
}
std::vector<float> out((size_t) c.n_instruments * cin * c.dim_f * T);
mdx_forward(&m, in.data(), T, out.data());
double pk = 0.0;
bool finite = true;
for (float v : out) {
if (!std::isfinite(v)) finite = false;
double a = std::fabs((double) v);
if (a > pk) pk = a;
}
printf("output %zu values, peak %.6f, %s\n", out.size(), pk,
finite ? "all finite" : "NON-FINITE");
if (!finite) return 1;
int rc = 0;
if (have_goldens) {
double max_abs = 0.0, sum = 0.0, ref_pk = 0.0;
for (size_t i = 0; i < out.size(); i++) {
double d = std::fabs((double) out[i] - (double) g.out[i]);
if (d > max_abs) max_abs = d;
sum += d;
double a = std::fabs((double) g.out[i]);
if (a > ref_pk) ref_pk = a;
}
const double mean = sum / (double) out.size();
const double rel = ref_pk > 0.0 ? max_abs / ref_pk : max_abs;
printf("vs PyTorch: max %.3e mean %.3e ref_pk %.3e rel %.2e %s\n",
max_abs, mean, ref_pk, rel, rel <= 2e-2 ? "ok" : "FAIL");
rc = rel <= 2e-2 ? 0 : 1;
} else {
printf("(no goldens supplied — shape/finiteness check only)\n");
}
// Reload guard: backend_init hands out a refcounted shared singleton, so a
// wrong teardown only shows up on the SECOND load in a process.
{
Mdx23c m2;
if (!mdx_load(&m2, argv[1])) {
fprintf(stderr, "reload FAILED — backend teardown is wrong\n");
mdx_free(&m);
return 1;
}
printf("reload ok\n");
mdx_free(&m2);
}
mdx_free(&m);
printf("%s\n", rc == 0 ? "PASS" : "FAIL");
return rc;
}
+111
View File
@@ -0,0 +1,111 @@
// mp3-codec.cpp: MP3 encoder/decoder CLI.
//
// Encode: mp3-codec -i input.wav -o output.mp3 [-b 128]
// Decode: mp3-codec -i input.mp3 -o output.wav
//
// Direction is auto-detected from output extension.
// Encoder: acestep mp3enc (MIT). Decoder: minimp3 (CC0).
#include "audio-io.h"
#include "version.h"
#include <cstdio>
#include <cstdlib>
#include <cstring>
static bool ends_with(const char * str, const char * suffix) {
int slen = (int) strlen(str);
int xlen = (int) strlen(suffix);
if (slen < xlen) {
return false;
}
for (int i = 0; i < xlen; i++) {
char a = str[slen - xlen + i];
char b = suffix[i];
if (a >= 'A' && a <= 'Z') {
a += 32;
}
if (b >= 'A' && b <= 'Z') {
b += 32;
}
if (a != b) {
return false;
}
}
return true;
}
int main(int argc, char ** argv) {
if (argc < 5) {
fprintf(stderr, "acestep.cpp %s\n\n", ACE_VERSION);
fprintf(stderr,
"Usage: %s -i <input> -o <output> [options]\n"
"\n"
" -i <path> Input file (WAV or MP3)\n"
" -o <path> Output file (WAV or MP3)\n"
" -b <kbps> Bitrate for MP3 encoding (default: 128)\n"
" --format <f> WAV format: wav16, wav24, wav32 (default: wav16)\n"
"\n"
"Mode is auto-detected from output extension.\n"
"\n"
"Examples:\n"
" %s -i song.wav -o song.mp3\n"
" %s -i song.wav -o song.mp3 -b 192\n"
" %s -i song.mp3 -o song.wav\n"
" %s -i song.mp3 -o song.wav --format wav32\n",
argv[0], argv[0], argv[0], argv[0], argv[0]);
return 1;
}
const char * input = NULL;
const char * output = NULL;
int bitrate = 128;
WavFormat wav_fmt = WAV_S16;
for (int i = 1; i < argc; i++) {
if (strcmp(argv[i], "-i") == 0 && i + 1 < argc) {
input = argv[++i];
} else if (strcmp(argv[i], "-o") == 0 && i + 1 < argc) {
output = argv[++i];
} else if (strcmp(argv[i], "-b") == 0 && i + 1 < argc) {
bitrate = atoi(argv[++i]);
} else if (strcmp(argv[i], "--format") == 0 && i + 1 < argc) {
bool dummy_mp3;
if (!audio_parse_format(argv[++i], dummy_mp3, wav_fmt)) {
fprintf(stderr, "[MP3-Codec] Unknown format: %s\n", argv[i]);
return 1;
}
} else {
fprintf(stderr, "[MP3-Codec] Unknown option: %s\n", argv[i]);
return 1;
}
}
if (!input || !output) {
fprintf(stderr, "[MP3-Codec] Both -i and -o are required\n");
return 1;
}
// read input (WAV or MP3, auto-detected)
int T = 0, sr = 0;
float * audio = audio_read(input, &T, &sr);
if (!audio) {
return 1;
}
// write output (WAV or MP3, auto-detected from extension)
bool ok;
if (ends_with(output, ".mp3")) {
ok = audio_write_mp3(output, audio, T, sr, bitrate);
} else if (ends_with(output, ".wav")) {
ok = audio_write_wav(output, audio, T, sr, wav_fmt);
} else {
fprintf(stderr, "[MP3-Codec] Cannot determine format from output extension\n");
fprintf(stderr, " use .mp3 for encoding, .wav for decoding\n");
free(audio);
return 1;
}
free(audio);
return ok ? 0 : 1;
}
+496
View File
@@ -0,0 +1,496 @@
// neural-codec.cpp: neural audio codec (Oobleck VAE)
//
// 2 modes:
// encode: WAV -> latent file (f32, Q8, or Q4)
// decode: latent file -> WAV (48kHz stereo)
//
// Three latent formats, decode auto-detects:
//
// f32 (default): flat [T, 64] f32, no header.
// T = file_size / 256. 25Hz, ~6.4 KB/s, ~51 kbit/s.
//
// Q8 (--q8): symmetric per-frame int8 quantization.
// header: "NAC8" magic (4B) + uint32 T_latent (4B)
// frame: f16 scale (2B) + int8[64] (64B) = 66B
// 25Hz, ~1.6 KB/s, ~13 kbit/s.
//
// Q4 (--q4): symmetric per-frame 4-bit quantization.
// header: "NAC4" magic (4B) + uint32 T_latent (4B)
// frame: f16 scale (2B) + nibbles[32] (32B) = 34B
// 25Hz, ~850 B/s, ~6.8 kbit/s.
//
// Usage:
// neural-codec --vae model.gguf --encode -i song.wav -o song.latent
// neural-codec --vae model.gguf --encode --q8 -i song.wav -o song.nac8
// neural-codec --vae model.gguf --encode --q4 -i song.wav -o song.nac4
// neural-codec --vae model.gguf --decode -i song.nac4 -o song.wav
#include "audio-io.h"
#include "vae-enc.h"
#include "vae.h"
#include "version.h"
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <string>
#include <vector>
// Q8 format constants
static const char NAC8_MAGIC[4] = { 'N', 'A', 'C', '8' };
static const int NAC8_HEADER = 8; // 4B magic + 4B T_latent
static const int NAC8_FRAME = 66; // 2B f16 scale + 64B int8
// Write Q8 quantized latent
static bool write_latent_q8(const char * path, const float * data, int T_latent) {
FILE * f = fopen(path, "wb");
if (!f) {
return false;
}
fwrite(NAC8_MAGIC, 1, 4, f);
uint32_t t = (uint32_t) T_latent;
fwrite(&t, 4, 1, f);
for (int i = 0; i < T_latent; i++) {
const float * frame = data + i * 64;
// find max abs for symmetric quant
float amax = 0.0f;
for (int j = 0; j < 64; j++) {
float a = fabsf(frame[j]);
if (a > amax) {
amax = a;
}
}
float scale = amax / 127.0f;
ggml_fp16_t scale_f16 = ggml_fp32_to_fp16(scale);
fwrite(&scale_f16, 2, 1, f);
// quantize
int8_t q[64];
float inv = (scale > 0.0f) ? 127.0f / amax : 0.0f;
for (int j = 0; j < 64; j++) {
int v = (int) roundf(frame[j] * inv);
q[j] = (int8_t) (v < -127 ? -127 : (v > 127 ? 127 : v));
}
fwrite(q, 1, 64, f);
}
fclose(f);
size_t bytes = NAC8_HEADER + (size_t) T_latent * NAC8_FRAME;
float duration = (float) T_latent * 1920.0f / 48000.0f;
float kbps = (float) bytes * 8.0f / (duration * 1000.0f);
fprintf(stderr, "[Latent] Wrote %s: Q8, %d frames (%.2fs, %.1f KB, %.1f kbit/s)\n", path, T_latent, duration,
(float) bytes / 1024.0f, kbps);
return true;
}
// Q4 format constants
static const char NAC4_MAGIC[4] = { 'N', 'A', 'C', '4' };
static const int NAC4_HEADER = 8; // 4B magic + 4B T_latent
static const int NAC4_FRAME = 34; // 2B f16 scale + 32B packed nibbles
// Write Q4 quantized latent
// Symmetric 4-bit: range [-7, 7], scale = amax / 7.0
// Packing: byte = (low & 0x0F) | (high << 4), two signed nibbles per byte
static bool write_latent_q4(const char * path, const float * data, int T_latent) {
FILE * f = fopen(path, "wb");
if (!f) {
return false;
}
fwrite(NAC4_MAGIC, 1, 4, f);
uint32_t t = (uint32_t) T_latent;
fwrite(&t, 4, 1, f);
for (int i = 0; i < T_latent; i++) {
const float * frame = data + i * 64;
// find max abs for symmetric quant
float amax = 0.0f;
for (int j = 0; j < 64; j++) {
float a = fabsf(frame[j]);
if (a > amax) {
amax = a;
}
}
float scale = amax / 7.0f;
ggml_fp16_t scale_f16 = ggml_fp32_to_fp16(scale);
fwrite(&scale_f16, 2, 1, f);
// quantize and pack pairs into bytes
float inv = (scale > 0.0f) ? 7.0f / amax : 0.0f;
uint8_t packed[32];
for (int j = 0; j < 32; j++) {
int lo = (int) roundf(frame[j * 2 + 0] * inv);
int hi = (int) roundf(frame[j * 2 + 1] * inv);
lo = lo < -7 ? -7 : (lo > 7 ? 7 : lo);
hi = hi < -7 ? -7 : (hi > 7 ? 7 : hi);
packed[j] = (uint8_t) ((lo & 0x0F) | (hi << 4));
}
fwrite(packed, 1, 32, f);
}
fclose(f);
size_t bytes = NAC4_HEADER + (size_t) T_latent * NAC4_FRAME;
float duration = (float) T_latent * 1920.0f / 48000.0f;
float kbps = (float) bytes * 8.0f / (duration * 1000.0f);
fprintf(stderr, "[Latent] Wrote %s: Q4, %d frames (%.2fs, %.1f KB, %.1f kbit/s)\n", path, T_latent, duration,
(float) bytes / 1024.0f, kbps);
return true;
}
// Write f32 raw latent (no header)
static bool write_latent_f32(const char * path, const float * data, int T_latent) {
FILE * f = fopen(path, "wb");
if (!f) {
return false;
}
size_t bytes = (size_t) T_latent * 64 * sizeof(float);
fwrite(data, 1, bytes, f);
fclose(f);
float duration = (float) T_latent * 1920.0f / 48000.0f;
fprintf(stderr, "[Latent] Wrote %s: f32, %d frames (%.2fs, %.1f KB, %.1f kbit/s)\n", path, T_latent, duration,
(float) bytes / 1024.0f, (float) bytes * 8.0f / (duration * 1000.0f));
return true;
}
// Read latent, auto-detect format (NAC8 -> Q8, NAC4 -> Q4, else f32).
// Returns [T_latent, 64] f32 (dequantized if quantized). Caller frees.
static float * read_latent(const char * path, int * T_latent) {
FILE * f = fopen(path, "rb");
if (!f) {
fprintf(stderr, "[Latent] Cannot open %s\n", path);
return NULL;
}
fseek(f, 0, SEEK_END);
long fsize = ftell(f);
fseek(f, 0, SEEK_SET);
// Check magic
char magic[4] = {};
if (fsize >= 8) {
if (fread(magic, 1, 4, f) != 4) {
fclose(f);
return NULL;
}
}
if (memcmp(magic, NAC8_MAGIC, 4) == 0) {
// Q8 format
uint32_t t;
if (fread(&t, 4, 1, f) != 1) {
fclose(f);
return NULL;
}
*T_latent = (int) t;
long expected = NAC8_HEADER + (long) t * NAC8_FRAME;
if (fsize != expected) {
fprintf(stderr, "[Latent] Q8 size mismatch: expected %ld, got %ld\n", expected, fsize);
fclose(f);
return NULL;
}
float * data = (float *) malloc((size_t) t * 64 * sizeof(float));
for (int i = 0; i < (int) t; i++) {
ggml_fp16_t scale_f16;
if (fread(&scale_f16, 2, 1, f) != 1) {
fclose(f);
free(data);
return NULL;
}
float scale = ggml_fp16_to_fp32(scale_f16);
int8_t q[64];
if (fread(q, 1, 64, f) != 64) {
fclose(f);
free(data);
return NULL;
}
float * frame = data + i * 64;
for (int j = 0; j < 64; j++) {
frame[j] = (float) q[j] * scale;
}
}
fclose(f);
float duration = (float) (*T_latent) * 1920.0f / 48000.0f;
float kbps = (float) fsize * 8.0f / (duration * 1000.0f);
fprintf(stderr, "[Latent] Read %s: Q8, %d frames (%.2fs, %.1f KB, %.1f kbit/s)\n", path, *T_latent, duration,
(float) fsize / 1024.0f, kbps);
return data;
}
if (memcmp(magic, NAC4_MAGIC, 4) == 0) {
// Q4 format
uint32_t t;
if (fread(&t, 4, 1, f) != 1) {
fclose(f);
return NULL;
}
*T_latent = (int) t;
long expected = NAC4_HEADER + (long) t * NAC4_FRAME;
if (fsize != expected) {
fprintf(stderr, "[Latent] Q4 size mismatch: expected %ld, got %ld\n", expected, fsize);
fclose(f);
return NULL;
}
float * data = (float *) malloc((size_t) t * 64 * sizeof(float));
for (int i = 0; i < (int) t; i++) {
ggml_fp16_t scale_f16;
if (fread(&scale_f16, 2, 1, f) != 1) {
fclose(f);
free(data);
return NULL;
}
float scale = ggml_fp16_to_fp32(scale_f16);
uint8_t packed[32];
if (fread(packed, 1, 32, f) != 32) {
fclose(f);
free(data);
return NULL;
}
// unpack signed nibbles
float * frame = data + i * 64;
for (int j = 0; j < 32; j++) {
int lo = (int) (packed[j] & 0x0F);
int hi = (int) (packed[j] >> 4);
if (lo >= 8) {
lo -= 16;
}
if (hi >= 8) {
hi -= 16;
}
frame[j * 2 + 0] = (float) lo * scale;
frame[j * 2 + 1] = (float) hi * scale;
}
}
fclose(f);
float duration = (float) (*T_latent) * 1920.0f / 48000.0f;
float kbps = (float) fsize * 8.0f / (duration * 1000.0f);
fprintf(stderr, "[Latent] Read %s: Q4, %d frames (%.2fs, %.1f KB, %.1f kbit/s)\n", path, *T_latent, duration,
(float) fsize / 1024.0f, kbps);
return data;
}
// f32 format (no header, rewind)
fseek(f, 0, SEEK_SET);
if (fsize % (64 * (int) sizeof(float)) != 0) {
fprintf(stderr, "[Latent] File size %ld not a multiple of %d (64 * f32)\n", fsize, (int) (64 * sizeof(float)));
fclose(f);
return NULL;
}
*T_latent = (int) (fsize / (64 * sizeof(float)));
float * data = (float *) malloc(fsize);
if (fread(data, 1, fsize, f) != (size_t) fsize) {
fclose(f);
free(data);
return NULL;
}
fclose(f);
float duration = (float) (*T_latent) * 1920.0f / 48000.0f;
fprintf(stderr, "[Latent] Read %s: f32, %d frames (%.2fs, %.1f KB, %.1f kbit/s)\n", path, *T_latent, duration,
(float) fsize / 1024.0f, (float) fsize * 8.0f / (duration * 1000.0f));
return data;
}
static void print_usage(const char * prog) {
fprintf(stderr, "acestep.cpp %s\n\n", ACE_VERSION);
fprintf(stderr,
"Usage: %s --vae <gguf> --encode|--decode -i <input> [-o <output>] [--q8|--q4]\n\n"
"Required:\n"
" --vae <path> VAE GGUF file\n"
" --encode | --decode Encode audio to latent, or decode latent to WAV\n"
" -i <path> Input (WAV/MP3 for encode, latent for decode)\n\n"
"Output:\n"
" -o <path> Output file (auto-named if omitted)\n"
" --q8 Quantize latent to int8 (~13 kbit/s)\n"
" --q4 Quantize latent to int4 (~6.8 kbit/s)\n"
" --format <fmt> WAV format: wav16, wav24, wav32 (default: wav16)\n\n"
"Output naming: song.wav -> song.latent (f32) or song.nac8 (Q8) or song.nac4 (Q4)\n"
" song.latent -> song.wav\n\n"
"Memory control:\n"
" --vae-chunk <N> Latent frames per tile (default: 256)\n"
" --vae-overlap <N> Overlap frames per side (default: 64)\n\n"
"Latent formats (decode auto-detects):\n"
" f32: flat [T, 64] f32, no header. ~51 kbit/s.\n"
" NAC8: header + per-frame Q8. ~13 kbit/s.\n"
" NAC4: header + per-frame Q4. ~6.8 kbit/s.\n",
prog);
}
static std::string auto_output(const char * input, const char * ext) {
std::string s = input;
size_t dot = s.rfind('.');
if (dot != std::string::npos) {
return s.substr(0, dot) + ext;
}
return s + ext;
}
int main(int argc, char ** argv) {
const char * vae_path = NULL;
const char * input_path = NULL;
const char * output_path = NULL;
int chunk_size = 256;
int overlap = 64;
int mode = -1; // 0 = encode, 1 = decode
int quant = 0; // 0 = f32, 8 = q8, 4 = q4
WavFormat wav_fmt = WAV_S16;
for (int i = 1; i < argc; i++) {
if (strcmp(argv[i], "--vae") == 0 && i + 1 < argc) {
vae_path = argv[++i];
} else if (strcmp(argv[i], "-i") == 0 && i + 1 < argc) {
input_path = argv[++i];
} else if (strcmp(argv[i], "--input") == 0 && i + 1 < argc) {
input_path = argv[++i];
} else if (strcmp(argv[i], "-o") == 0 && i + 1 < argc) {
output_path = argv[++i];
} else if (strcmp(argv[i], "--output") == 0 && i + 1 < argc) {
output_path = argv[++i];
} else if (strcmp(argv[i], "--format") == 0 && i + 1 < argc) {
bool dummy_mp3;
if (!audio_parse_format(argv[++i], dummy_mp3, wav_fmt)) {
fprintf(stderr, "Unknown format: %s\n", argv[i]);
print_usage(argv[0]);
return 1;
}
} else if (strcmp(argv[i], "--vae-chunk") == 0 && i + 1 < argc) {
chunk_size = atoi(argv[++i]);
} else if (strcmp(argv[i], "--vae-overlap") == 0 && i + 1 < argc) {
overlap = atoi(argv[++i]);
} else if (strcmp(argv[i], "--encode") == 0) {
mode = 0;
} else if (strcmp(argv[i], "--decode") == 0) {
mode = 1;
} else if (strcmp(argv[i], "--q8") == 0) {
quant = 8;
} else if (strcmp(argv[i], "--q4") == 0) {
quant = 4;
} else if (strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "--help") == 0) {
print_usage(argv[0]);
return 0;
} else {
fprintf(stderr, "Unknown arg: %s\n", argv[i]);
print_usage(argv[0]);
return 1;
}
}
if (!vae_path || !input_path || mode < 0) {
print_usage(argv[0]);
return 1;
}
// Auto output names
std::string out_str;
if (!output_path) {
if (mode == 0) {
const char * ext = ".latent";
if (quant == 8) {
ext = ".nac8";
}
if (quant == 4) {
ext = ".nac4";
}
out_str = auto_output(input_path, ext);
} else {
out_str = auto_output(input_path, ".wav");
}
output_path = out_str.c_str();
}
const char * quant_str = "";
if (mode == 0 && quant == 8) {
quant_str = " (Q8)";
}
if (mode == 0 && quant == 4) {
quant_str = " (Q4)";
}
fprintf(stderr, "\n[VAE] Mode: %s%s\n", mode == 0 ? "encode" : "decode", quant_str);
fprintf(stderr, "[VAE] Input: %s\n", input_path);
fprintf(stderr, "[VAE] Output: %s\n\n", output_path);
// ENCODE
if (mode == 0) {
int T_audio = 0;
float * planar = audio_read_48k(input_path, &T_audio);
if (!planar) {
return 1;
}
float * audio = audio_planar_to_interleaved(planar, T_audio);
free(planar);
VAEEncoder enc = {};
vae_enc_load(&enc, vae_path);
int max_T = (T_audio / 1920) + 64;
std::vector<float> latent((size_t) max_T * 64);
fprintf(stderr, "\n[VAE] Encoding %d samples (%.2fs)...\n", T_audio, (float) T_audio / 48000.0f);
int T_latent = vae_enc_encode_tiled(&enc, audio, T_audio, latent.data(), max_T, chunk_size, overlap);
free(audio);
if (T_latent < 0) {
vae_enc_free(&enc);
return 1;
}
if (quant == 8) {
write_latent_q8(output_path, latent.data(), T_latent);
} else if (quant == 4) {
write_latent_q4(output_path, latent.data(), T_latent);
} else {
write_latent_f32(output_path, latent.data(), T_latent);
}
vae_enc_free(&enc);
fprintf(stderr, "[VAE] Done.\n");
return 0;
}
// DECODE (auto-detects f32 vs Q8 vs Q4 from file content)
{
int T_latent = 0;
float * latent = read_latent(input_path, &T_latent);
if (!latent) {
return 1;
}
VAEGGML dec = {};
vae_ggml_load(&dec, vae_path);
int max_T = T_latent * 1920 + 4096;
std::vector<float> audio((size_t) 2 * max_T, 0.0f);
fprintf(stderr, "\n[VAE] Decoding %d latent frames...\n", T_latent);
int T_audio = vae_ggml_decode_tiled(&dec, latent, T_latent, audio.data(), max_T, chunk_size, overlap);
free(latent);
if (T_audio < 0) {
vae_ggml_free(&dec);
return 1;
}
if (audio_write(output_path, audio.data(), T_audio, 48000, 0, wav_fmt)) {
fprintf(stderr, "\n[VAE] Output: %s (%d samples, %.2fs @ 48kHz)\n", output_path, T_audio,
(float) T_audio / 48000.0f);
} else {
fprintf(stderr, "[VAE] FATAL: failed to write %s\n", output_path);
}
vae_ggml_free(&dec);
fprintf(stderr, "[VAE] Done.\n");
return 0;
}
}
Binary file not shown.
+419
View File
@@ -0,0 +1,419 @@
// 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 NVFP4 MXFP4
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <vector>
#ifdef _WIN32
# include <windows.h>
# define strcasecmp _stricmp
#else
# include <fcntl.h>
# include <sys/mman.h>
# include <sys/stat.h>
# include <unistd.h>
#endif
#include "ggml.h"
#include "gguf.h"
#include "version.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 },
{ "NVFP4", GGML_TYPE_NVFP4, GGML_TYPE_COUNT, GGML_TYPE_Q8_0, 0, 0 },
{ "MXFP4", GGML_TYPE_MXFP4, 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, "acestep.cpp %s\n\n", ACE_VERSION);
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;
}
+454
View File
@@ -0,0 +1,454 @@
// sa3-ggml-test.cpp: parity test CLI for the StableStep GGML SA3 modules.
//
// Runs GGML ports of the SA3 conditioning modules against golden vectors
// dumped from the validated ONNX graphs (see tools/onnx-export/), and reports
// cosine similarity + max abs diff per component.
//
// Usage:
// sa3-ggml-test --models <dir-with-sa3-*.gguf> --goldens <dir-with-manifest.json>
// [--component text_enc|seconds|same_enc|same_dec|dit|all]
//
// Components:
// text_enc: sa3-text-enc-BF16.gguf, T5Gemma encoder + learned padding
// substitution. Inputs input_ids [1,S] i64 + attention_mask [1,S]
// u8, expected embeddings [1,S,768] f32.
// seconds: sa3-dit-BF16.gguf (embedder tensors only), NumberConditioner
// expo Fourier embedder. Input [1] f32, expected [1,768] f32.
// same_enc: sa3-same-enc-F16.gguf, SAME-L encoder. Input audio
// [1,2,524288] f32, expected latents [1,256,128] f32.
// same_dec: sa3-same-dec-F16.gguf, SAME-L decoder. Input latents
// [1,256,128] f32, expected audio [1,2,524288] f32.
// dit: sa3-dit-BF16.gguf, DiffusionTransformer single forward. Inputs
// x [1,256,T] f32, t [1] f32, cross_attn_cond [1,S,768] f32,
// global_embed [1,768] f32, local_add_cond [1,257,T] f32,
// padding_mask [1,T] u8; expected v [1,256,T] f32.
//
// Exit code 0 only if every run component passes cosine > 0.999.
#include "sa3-dit-ggml.h"
#include "sa3-same-ggml.h"
#include "sa3-t5gemma-enc.h"
#include "yyjson.h"
#include <cmath>
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <string>
#include <vector>
static const double PASS_COSINE = 0.999;
static bool read_file(const std::string & path, std::vector<uint8_t> & out) {
FILE * f = fopen(path.c_str(), "rb");
if (!f) {
fprintf(stderr, "[Test] cannot open %s\n", path.c_str());
return false;
}
fseek(f, 0, SEEK_END);
long sz = ftell(f);
fseek(f, 0, SEEK_SET);
out.resize((size_t) sz);
size_t rd = fread(out.data(), 1, (size_t) sz, f);
fclose(f);
if (rd != (size_t) sz) {
fprintf(stderr, "[Test] short read on %s\n", path.c_str());
return false;
}
return true;
}
struct Metrics {
double cosine;
double max_abs_diff;
};
static Metrics compare(const float * a, const float * b, size_t n) {
double dot = 0, na = 0, nb = 0, mad = 0;
for (size_t i = 0; i < n; i++) {
dot += (double) a[i] * b[i];
na += (double) a[i] * a[i];
nb += (double) b[i] * b[i];
double d = fabs((double) a[i] - b[i]);
if (d > mad) {
mad = d;
}
}
Metrics m;
m.cosine = (na > 0 && nb > 0) ? dot / (sqrt(na) * sqrt(nb)) : 0.0;
m.max_abs_diff = mad;
return m;
}
// Manifest helpers: get golden["<component>"]["inputs"/"outputs"]["<name>"]["file"]
static std::string manifest_file(yyjson_val * root, const char * comp, const char * io, const char * name) {
yyjson_val * c = yyjson_obj_get(root, comp);
yyjson_val * g = c ? yyjson_obj_get(c, io) : NULL;
yyjson_val * t = g ? yyjson_obj_get(g, name) : NULL;
yyjson_val * f = t ? yyjson_obj_get(t, "file") : NULL;
return (f && yyjson_is_str(f)) ? yyjson_get_str(f) : "";
}
static int64_t manifest_shape_prod(yyjson_val * root, const char * comp, const char * io, const char * name) {
yyjson_val * c = yyjson_obj_get(root, comp);
yyjson_val * g = c ? yyjson_obj_get(c, io) : NULL;
yyjson_val * t = g ? yyjson_obj_get(g, name) : NULL;
yyjson_val * s = t ? yyjson_obj_get(t, "shape") : NULL;
if (!s || !yyjson_is_arr(s)) {
return 0;
}
int64_t prod = 1;
size_t idx, max;
yyjson_val * d;
yyjson_arr_foreach(s, idx, max, d) {
prod *= yyjson_get_int(d);
}
return prod;
}
static bool run_text_enc(const std::string & models, const std::string & goldens, yyjson_val * root, Metrics * out) {
std::string ids_f = manifest_file(root, "text_enc", "inputs", "input_ids");
std::string mask_f = manifest_file(root, "text_enc", "inputs", "attention_mask");
std::string exp_f = manifest_file(root, "text_enc", "outputs", "embeddings");
if (ids_f.empty() || mask_f.empty() || exp_f.empty()) {
fprintf(stderr, "[Test] text_enc: manifest missing entries\n");
return false;
}
int64_t S = manifest_shape_prod(root, "text_enc", "inputs", "input_ids");
int64_t n_out = manifest_shape_prod(root, "text_enc", "outputs", "embeddings");
if (S <= 0 || n_out <= 0 || n_out % S != 0) {
fprintf(stderr, "[Test] text_enc: bad shapes in manifest\n");
return false;
}
int64_t H = n_out / S;
std::vector<uint8_t> ids_raw, mask_raw, exp_raw;
if (!read_file(goldens + "/" + ids_f, ids_raw) || !read_file(goldens + "/" + mask_f, mask_raw) ||
!read_file(goldens + "/" + exp_f, exp_raw)) {
return false;
}
if (ids_raw.size() != (size_t) S * 8 || mask_raw.size() != (size_t) S || exp_raw.size() != (size_t) n_out * 4) {
fprintf(stderr, "[Test] text_enc: golden file sizes do not match manifest shapes\n");
return false;
}
std::vector<int32_t> ids((size_t) S);
const int64_t * ids64 = (const int64_t *) ids_raw.data();
for (int64_t i = 0; i < S; i++) {
ids[(size_t) i] = (int32_t) ids64[i];
}
SA3T5GemmaEnc enc = {};
if (!sa3_t5gemma_load(&enc, (models + "/sa3-text-enc-BF16.gguf").c_str())) {
return false;
}
if ((int64_t) enc.cfg.hidden_size != H) {
fprintf(stderr, "[Test] text_enc: model H=%d but golden H=%lld\n", enc.cfg.hidden_size, (long long) H);
sa3_t5gemma_free(&enc);
return false;
}
const char * env_layers = getenv("SA3_T5G_LAYERS");
if (env_layers) {
enc.debug_n_layers = atoi(env_layers);
fprintf(stderr, "[Test] text_enc: DEBUG truncated to %d layers\n", enc.debug_n_layers);
}
std::vector<float> got((size_t) n_out);
sa3_t5gemma_forward(&enc, ids.data(), mask_raw.data(), (int) S, got.data());
sa3_t5gemma_free(&enc);
*out = compare(got.data(), (const float *) exp_raw.data(), (size_t) n_out);
return true;
}
static bool run_seconds(const std::string & models, const std::string & goldens, yyjson_val * root, Metrics * out) {
std::string in_f = manifest_file(root, "seconds", "inputs", "seconds");
std::string exp_f = manifest_file(root, "seconds", "outputs", "embed");
if (in_f.empty() || exp_f.empty()) {
fprintf(stderr, "[Test] seconds: manifest missing entries\n");
return false;
}
std::vector<uint8_t> in_raw, exp_raw;
if (!read_file(goldens + "/" + in_f, in_raw) || !read_file(goldens + "/" + exp_f, exp_raw)) {
return false;
}
if (in_raw.size() != 4) {
fprintf(stderr, "[Test] seconds: bad input size\n");
return false;
}
float seconds = *(const float *) in_raw.data();
int64_t n_out = manifest_shape_prod(root, "seconds", "outputs", "embed");
if (n_out <= 0 || exp_raw.size() != (size_t) n_out * 4) {
fprintf(stderr, "[Test] seconds: golden size mismatch\n");
return false;
}
SA3SecondsEmbedder emb;
if (!sa3_seconds_embedder_load(&emb, (models + "/sa3-dit-BF16.gguf").c_str())) {
return false;
}
if ((int64_t) emb.out_dim != n_out) {
fprintf(stderr, "[Test] seconds: model out=%d but golden %lld\n", emb.out_dim, (long long) n_out);
return false;
}
std::vector<float> got((size_t) n_out);
sa3_seconds_embed(emb, seconds, got.data());
*out = compare(got.data(), (const float *) exp_raw.data(), (size_t) n_out);
return true;
}
// Shared runner for the SAME autoencoder halves. is_encoder selects the
// direction; the golden manifest supplies both tensors' shapes.
static bool run_same(const std::string & models, const std::string & goldens, yyjson_val * root,
bool is_encoder, Metrics * out) {
const char * comp = is_encoder ? "same_enc" : "same_dec";
const char * in_name = is_encoder ? "audio" : "latents";
const char * out_name = is_encoder ? "latents" : "audio";
std::string in_f = manifest_file(root, comp, "inputs", in_name);
std::string exp_f = manifest_file(root, comp, "outputs", out_name);
if (in_f.empty() || exp_f.empty()) {
fprintf(stderr, "[Test] %s: manifest missing entries\n", comp);
return false;
}
int64_t n_in = manifest_shape_prod(root, comp, "inputs", in_name);
int64_t n_out = manifest_shape_prod(root, comp, "outputs", out_name);
int64_t n_lat = is_encoder ? n_out : n_in;
if (n_in <= 0 || n_out <= 0 || n_lat % 256 != 0) {
fprintf(stderr, "[Test] %s: bad shapes in manifest\n", comp);
return false;
}
int n_latents = (int) (n_lat / 256); // latent_dim 256
std::vector<uint8_t> in_raw, exp_raw;
if (!read_file(goldens + "/" + in_f, in_raw) || !read_file(goldens + "/" + exp_f, exp_raw)) {
return false;
}
if (in_raw.size() != (size_t) n_in * 4 || exp_raw.size() != (size_t) n_out * 4) {
fprintf(stderr, "[Test] %s: golden file sizes do not match manifest shapes\n", comp);
return false;
}
SA3Same same = {};
// F16 is the current conversion (see convert-sa3.py); fall back to the
// older BF16 name if that is what is on disk.
std::string gguf = models + (is_encoder ? "/sa3-same-enc-F16.gguf" : "/sa3-same-dec-F16.gguf");
{
FILE * f = fopen(gguf.c_str(), "rb");
if (f) {
fclose(f);
} else {
gguf = models + (is_encoder ? "/sa3-same-enc-BF16.gguf" : "/sa3-same-dec-BF16.gguf");
}
}
if (!sa3_same_load(&same, gguf.c_str(), is_encoder)) {
return false;
}
// Debug hooks: SA3_SAME_STAGE=<n> dumps the token sequence after n layers
// (0 = folded input) to SA3_SAME_DUMP (default sa3_same_stage.bin).
const char * env_stage = getenv("SA3_SAME_STAGE");
if (env_stage) {
same.debug_stage = atoi(env_stage);
fprintf(stderr, "[Test] %s: DEBUG dumping stage %d\n", comp, same.debug_stage);
}
std::vector<float> got((size_t) n_out);
sa3_same_forward(&same, (const float *) in_raw.data(), got.data(), n_latents);
if (env_stage && !same.debug_out.empty()) {
const char * dump = getenv("SA3_SAME_DUMP");
std::string path = dump ? dump : "sa3_same_stage.bin";
FILE * f = fopen(path.c_str(), "wb");
if (f) {
fwrite(same.debug_out.data(), sizeof(float), same.debug_out.size(), f);
fclose(f);
fprintf(stderr, "[Test] %s: stage tensor (%zu floats) -> %s\n", comp, same.debug_out.size(),
path.c_str());
}
}
sa3_same_free(&same);
*out = compare(got.data(), (const float *) exp_raw.data(), (size_t) n_out);
return true;
}
static bool run_dit(const std::string & models, const std::string & goldens, yyjson_val * root, Metrics * out) {
const char * in_names[] = { "x", "t", "cross_attn_cond", "global_embed", "local_add_cond", "padding_mask" };
std::vector<std::vector<uint8_t>> raw(7);
for (int i = 0; i < 6; i++) {
std::string f = manifest_file(root, "dit", "inputs", in_names[i]);
if (f.empty() || !read_file(goldens + "/" + f, raw[(size_t) i])) {
fprintf(stderr, "[Test] dit: missing input '%s'\n", in_names[i]);
return false;
}
}
std::string exp_f = manifest_file(root, "dit", "outputs", "v");
if (exp_f.empty() || !read_file(goldens + "/" + exp_f, raw[6])) {
fprintf(stderr, "[Test] dit: missing output 'v'\n");
return false;
}
int64_t n_x = manifest_shape_prod(root, "dit", "inputs", "x");
int64_t n_cross = manifest_shape_prod(root, "dit", "inputs", "cross_attn_cond");
int64_t n_glob = manifest_shape_prod(root, "dit", "inputs", "global_embed");
int64_t n_local = manifest_shape_prod(root, "dit", "inputs", "local_add_cond");
int64_t T = manifest_shape_prod(root, "dit", "inputs", "padding_mask");
int64_t n_out = manifest_shape_prod(root, "dit", "outputs", "v");
if (T <= 0 || n_x != 256 * T || n_out != n_x || n_glob != 768 || n_cross % 768 != 0 ||
n_local != 257 * T) {
fprintf(stderr, "[Test] dit: bad shapes in manifest\n");
return false;
}
int64_t S_c = n_cross / 768;
if (raw[0].size() != (size_t) n_x * 4 || raw[1].size() != 4 || raw[2].size() != (size_t) n_cross * 4 ||
raw[3].size() != (size_t) n_glob * 4 || raw[4].size() != (size_t) n_local * 4 ||
raw[5].size() != (size_t) T || raw[6].size() != (size_t) n_out * 4) {
fprintf(stderr, "[Test] dit: golden file sizes do not match manifest shapes\n");
return false;
}
SA3DiT dit = {};
if (!sa3_dit_load(&dit, (models + "/sa3-dit-BF16.gguf").c_str())) {
return false;
}
// Debug hook: SA3_DIT_STAGE=<n> dumps the token sequence after n layers
// (0 = memory+projected input) to SA3_DIT_DUMP (default sa3_dit_stage.bin).
const char * env_stage = getenv("SA3_DIT_STAGE");
if (env_stage) {
dit.debug_stage = atoi(env_stage);
fprintf(stderr, "[Test] dit: DEBUG dumping stage %d\n", dit.debug_stage);
}
std::vector<float> got((size_t) n_out);
sa3_dit_forward(&dit, (const float *) raw[0].data(), *(const float *) raw[1].data(),
(const float *) raw[2].data(), S_c, (const float *) raw[3].data(),
(const float *) raw[4].data(), raw[5].data(), T, got.data());
if (env_stage && !dit.debug_out.empty()) {
const char * dump = getenv("SA3_DIT_DUMP");
std::string path = dump ? dump : "sa3_dit_stage.bin";
FILE * f = fopen(path.c_str(), "wb");
if (f) {
fwrite(dit.debug_out.data(), sizeof(float), dit.debug_out.size(), f);
fclose(f);
fprintf(stderr, "[Test] dit: stage tensor (%zu floats) -> %s\n", dit.debug_out.size(),
path.c_str());
}
}
sa3_dit_free(&dit);
*out = compare(got.data(), (const float *) raw[6].data(), (size_t) n_out);
return true;
}
int main(int argc, char ** argv) {
std::string models, goldens, component = "all";
for (int i = 1; i < argc; i++) {
if (!strcmp(argv[i], "--models") && i + 1 < argc) {
models = argv[++i];
} else if (!strcmp(argv[i], "--goldens") && i + 1 < argc) {
goldens = argv[++i];
} else if (!strcmp(argv[i], "--component") && i + 1 < argc) {
component = argv[++i];
} else {
fprintf(stderr,
"Usage: sa3-ggml-test --models <dir> --goldens <dir> [--component text_enc|seconds|same_enc|same_dec|dit|all]\n");
return 2;
}
}
if (models.empty() || goldens.empty()) {
fprintf(stderr, "Usage: sa3-ggml-test --models <dir> --goldens <dir> [--component text_enc|seconds|same_enc|same_dec|dit|all]\n");
return 2;
}
std::vector<uint8_t> manifest_raw;
if (!read_file(goldens + "/manifest.json", manifest_raw)) {
return 2;
}
yyjson_doc * doc = yyjson_read((const char *) manifest_raw.data(), manifest_raw.size(), 0);
if (!doc) {
fprintf(stderr, "[Test] cannot parse manifest.json\n");
return 2;
}
yyjson_val * root = yyjson_doc_get_root(doc);
bool all_pass = true;
bool any_run = false;
if (component == "all" || component == "text_enc") {
Metrics m;
any_run = true;
if (run_text_enc(models, goldens, root, &m)) {
bool pass = m.cosine > PASS_COSINE;
printf("text_enc: cosine=%.6f max_abs_diff=%.6f %s\n", m.cosine, m.max_abs_diff,
pass ? "PASS" : "FAIL");
all_pass = all_pass && pass;
} else {
printf("text_enc: ERROR\n");
all_pass = false;
}
}
if (component == "all" || component == "seconds") {
Metrics m;
any_run = true;
if (run_seconds(models, goldens, root, &m)) {
bool pass = m.cosine > PASS_COSINE;
printf("seconds: cosine=%.6f max_abs_diff=%.6f %s\n", m.cosine, m.max_abs_diff,
pass ? "PASS" : "FAIL");
all_pass = all_pass && pass;
} else {
printf("seconds: ERROR\n");
all_pass = false;
}
}
if (component == "all" || component == "same_enc") {
Metrics m;
any_run = true;
if (run_same(models, goldens, root, true, &m)) {
bool pass = m.cosine > PASS_COSINE;
printf("same_enc: cosine=%.6f max_abs_diff=%.6f %s\n", m.cosine, m.max_abs_diff,
pass ? "PASS" : "FAIL");
all_pass = all_pass && pass;
} else {
printf("same_enc: ERROR\n");
all_pass = false;
}
}
if (component == "all" || component == "same_dec") {
Metrics m;
any_run = true;
if (run_same(models, goldens, root, false, &m)) {
bool pass = m.cosine > PASS_COSINE;
printf("same_dec: cosine=%.6f max_abs_diff=%.6f %s\n", m.cosine, m.max_abs_diff,
pass ? "PASS" : "FAIL");
all_pass = all_pass && pass;
} else {
printf("same_dec: ERROR\n");
all_pass = false;
}
}
if (component == "all" || component == "dit") {
Metrics m;
any_run = true;
if (run_dit(models, goldens, root, &m)) {
bool pass = m.cosine > PASS_COSINE;
printf("dit: cosine=%.6f max_abs_diff=%.6f %s\n", m.cosine, m.max_abs_diff,
pass ? "PASS" : "FAIL");
all_pass = all_pass && pass;
} else {
printf("dit: ERROR\n");
all_pass = false;
}
}
yyjson_doc_free(doc);
if (!any_run) {
fprintf(stderr, "[Test] unknown component '%s'\n", component.c_str());
return 2;
}
return all_pass ? 0 : 1;
}
+120
View File
@@ -0,0 +1,120 @@
#pragma once
// synth-batch-runner.h: three-phase orchestration shared by the synth binaries
//
// Phase 1 (all groups) runs ace_synth_job_run_dit. Each call acquires the DiT,
// runs the denoising loop AND LRC alignment (via ops_lrc_extract, while the DiT
// is still held), then releases it. Phase 2 (all groups) runs
// ace_synth_job_run_vae, which acquires the VAE decoder on entry and releases it
// on exit. Phase 3 (LRC) simply copies the pre-computed alignment from
// SynthState — no DiT acquisition needed.
// Under EVICT_STRICT, at most one GPU module is resident at a time.
#include "pipeline-synth.h"
#include <cstdio>
#include <vector>
// Run a batch of request groups through the synthesis phases.
//
// groups[g][i]: request i of group g. All requests in a group must share
// the same T (same audio_codes or same duration), which the ops assume
// when they stack per-batch tensors for a single DiT forward.
// seed must be resolved (non-negative) on every request.
// src_audio / ref_audio: interleaved stereo 48kHz buffers, NULL when not applicable.
// src_latents / ref_latents: pre-encoded latents [T_latent * 64] f32 alternative
// to the matching audio buffer. When non-NULL, the corresponding VAE encoder
// pass is skipped for every group. The same buffers are shared across groups,
// matching how src_audio and ref_audio are shared today.
// audio_out[sum_g(groups[g].size())]: pre-allocated slots filled by phase 2.
// On error, slots completed before the failure keep their audio; the rest
// are left at {NULL, 0, 0}. Caller owns ace_audio_free.
// latents_out: optional capture of one post-DiT latent per generated track,
// indexed identically to audio_out. Each entry is [T_track * 64] f32 time-major,
// T_track = entry.size() / 64. Pass NULL to skip the capture.
// Returns 0 on success, -1 on any error or cancellation.
static int synth_batch_run(AceSynth * ctx,
std::vector<std::vector<AceRequest>> & groups,
const float * src_audio,
int src_len,
const float * src_latents,
int src_T_latent,
const float * ref_audio,
int ref_len,
const float * ref_latents,
int ref_T_latent,
AceAudio * audio_out,
std::string * lrc_out = nullptr,
std::vector<std::vector<float>> * latents_out = nullptr,
bool (*cancel)(void *) = nullptr,
void * cancel_data = nullptr) {
const int n_groups = (int) groups.size();
std::vector<AceSynthJob *> jobs(n_groups, nullptr);
std::vector<int> audio_off(n_groups, 0);
if (latents_out) {
latents_out->clear();
}
// Phase 1: denoising + inline LRC for each group. ops_dit_generate
// acquires the DiT, runs the denoising loop, then calls ops_lrc_extract
// while the DiT is still held — avoiding a redundant adapter merge+reload
// under EVICT_STRICT. Results are cached in SynthState.lrc_results[].
int off = 0;
for (int g = 0; g < n_groups; g++) {
const int gn = (int) groups[g].size();
jobs[g] = ace_synth_job_run_dit(ctx, groups[g].data(), src_audio, src_len,
src_latents, src_T_latent,
ref_audio, ref_len,
ref_latents, ref_T_latent,
gn, cancel, cancel_data);
if (!jobs[g]) {
for (int j = 0; j < g; j++) {
ace_synth_job_free(jobs[j]);
}
return -1;
}
audio_off[g] = off;
off += gn;
}
// Capture one post-DiT latent per track, time-major [T*64], indexed to
// match audio_out. Latents live in jobs[g]->state.output until run_vae
// frees the job; capture happens before phase 2.
if (latents_out) {
latents_out->resize((size_t) off);
for (int g = 0; g < n_groups; g++) {
const int gn = (int) groups[g].size();
for (int i = 0; i < gn; i++) {
int T = 0;
const float * src = ace_synth_job_get_latent(jobs[g], i, &T);
(*latents_out)[audio_off[g] + i].assign(src, src + (size_t) T * 64);
}
}
}
// Phase 2: VAE decode for each job. The decoder is acquired and released
// by ops_vae_decode inside ace_synth_job_run_vae.
for (int g = 0; g < n_groups; g++) {
const int gn = (int) groups[g].size();
const int rc =
ace_synth_job_run_vae(ctx, jobs[g], audio_out + audio_off[g], cancel, cancel_data);
if (rc != 0) {
ace_synth_job_free(jobs[g]);
jobs[g] = nullptr;
for (int j = g + 1; j < n_groups; j++) {
ace_synth_job_free(jobs[j]);
}
return -1;
}
// Phase 3: LRC — copy pre-computed alignment (no DiT acquisition)
if (lrc_out && groups[g][0].get_lrc) {
ace_synth_job_run_lrc(ctx, jobs[g], lrc_out + audio_off[g], gn);
}
ace_synth_job_free(jobs[g]);
jobs[g] = nullptr;
}
return 0;
}
+38
View File
@@ -0,0 +1,38 @@
# Generate version.h with the current git commit hash and date.
# Only rewrites the file if the content changed (avoids rebuild cascade).
# Usage: cmake -DSRC_DIR=... -DOUTPUT=... -P version.cmake
execute_process(
COMMAND git rev-parse --short HEAD
WORKING_DIRECTORY "${SRC_DIR}"
OUTPUT_VARIABLE GIT_HASH
OUTPUT_STRIP_TRAILING_WHITESPACE
ERROR_QUIET
RESULT_VARIABLE GIT_RESULT
)
if(NOT GIT_RESULT EQUAL 0)
set(GIT_HASH "unknown")
endif()
execute_process(
COMMAND git show -s --format=%cs HEAD
WORKING_DIRECTORY "${SRC_DIR}"
OUTPUT_VARIABLE GIT_DATE
OUTPUT_STRIP_TRAILING_WHITESPACE
ERROR_QUIET
RESULT_VARIABLE DATE_RESULT
)
if(NOT DATE_RESULT EQUAL 0)
set(GIT_DATE "unknown")
endif()
set(CONTENT "#pragma once\n#define ACE_VERSION \"${GIT_HASH} (${GIT_DATE})\"\n")
if(EXISTS "${OUTPUT}")
file(READ "${OUTPUT}" EXISTING)
if("${EXISTING}" STREQUAL "${CONTENT}")
return()
endif()
endif()
file(WRITE "${OUTPUT}" "${CONTENT}")
File diff suppressed because it is too large Load Diff
+2
View File
@@ -0,0 +1,2 @@
dist/
package-lock.json
+15
View File
@@ -0,0 +1,15 @@
{
"useTabs": true,
"singleQuote": true,
"trailingComma": "none",
"printWidth": 100,
"plugins": ["prettier-plugin-svelte"],
"overrides": [
{
"files": "*.svelte",
"options": {
"parser": "svelte"
}
}
]
}
+6
View File
@@ -0,0 +1,6 @@
# Dice examples
JSON prompts loaded by the Dice button at build time.
Any `.json` file in this directory or its subdirectories is included.
Add your own prompts in acestep.cpp format and rebuild the webui.
@@ -0,0 +1,5 @@
{
"description": "a soft Bengali love song for a quiet evening",
"instrumental": false,
"vocal_language": "bn"
}
@@ -0,0 +1,5 @@
{
"description": "an upbeat summer pop song with catchy hooks",
"instrumental": false,
"vocal_language": "en"
}
@@ -0,0 +1,5 @@
{
"description": "epic orchestral cinematic music for a movie trailer",
"instrumental": true,
"vocal_language": "unknown"
}
@@ -0,0 +1,5 @@
{
"description": "一首深情的中文抒情歌曲,适合夜晚独自聆听",
"instrumental": false,
"vocal_language": "zh"
}
@@ -0,0 +1,5 @@
{
"description": "Japanese city pop with nostalgic 80s vibes",
"instrumental": false,
"vocal_language": "ja"
}
@@ -0,0 +1,5 @@
{
"description": "lo-fi hip hop beats for studying and relaxing",
"instrumental": true,
"vocal_language": "unknown"
}
@@ -0,0 +1,5 @@
{
"description": "energetic K-pop dance track with powerful vocals",
"instrumental": false,
"vocal_language": "ko"
}
@@ -0,0 +1,5 @@
{
"description": "romantic Spanish guitar ballad with heartfelt lyrics",
"instrumental": false,
"vocal_language": "es"
}
@@ -0,0 +1,5 @@
{
"description": "中国风电子舞曲,融合古典乐器与现代节拍",
"instrumental": false,
"vocal_language": "zh"
}
@@ -0,0 +1,5 @@
{
"description": "peaceful piano melody for meditation and relaxation",
"instrumental": true,
"vocal_language": "unknown"
}
@@ -0,0 +1,5 @@
{
"description": "Kraftvoller deutscher Metal-Anthem mit aggressivem Gesang und Double-Bass-Drums",
"instrumental": false,
"vocal_language": "de"
}
@@ -0,0 +1,5 @@
{
"description": "Energiegeladene Neue Deutsche Welle mit Retro-Synths und punkiger Attitüde",
"instrumental": false,
"vocal_language": "de"
}
@@ -0,0 +1,5 @@
{
"description": "Entspannter deutscher Hip-Hop mit lässigem Flow und jazzigen Samples",
"instrumental": false,
"vocal_language": "de"
}
@@ -0,0 +1,5 @@
{
"description": "Atmosphärische deutsche Elektronik mit Ambient-Texturen und sanftem Gesang",
"instrumental": false,
"vocal_language": "de"
}
@@ -0,0 +1,5 @@
{
"description": "Chanson française romantique avec accordéon et cordes nostalgiques",
"instrumental": false,
"vocal_language": "fr"
}
@@ -0,0 +1,5 @@
{
"description": "French house groovy avec samples disco et ligne de basse funky",
"instrumental": true,
"vocal_language": "fr"
}
@@ -0,0 +1,5 @@
{
"description": "Rap français poétique avec samples jazz et flow smooth",
"instrumental": false,
"vocal_language": "fr"
}
@@ -0,0 +1,5 @@
{
"description": "Pop française rêveuse avec voix éthérées et synthés luxuriants",
"instrumental": false,
"vocal_language": "fr"
}
@@ -0,0 +1,5 @@
{
"description": "Style yé-yé rétro avec rythme entraînant et voix enjouées",
"instrumental": false,
"vocal_language": "fr"
}
@@ -0,0 +1,5 @@
{
"description": "Ballade française mélancolique avec piano et émotion",
"instrumental": false,
"vocal_language": "fr"
}
@@ -0,0 +1,5 @@
{
"description": "a melancholic indie rock ballad with atmospheric guitars and dreamy reverb",
"instrumental": false,
"vocal_language": "en"
}
@@ -0,0 +1,5 @@
{
"description": "Électropop française énergique avec hooks accrocheurs",
"instrumental": false,
"vocal_language": "fr"
}
@@ -0,0 +1,5 @@
{
"description": "Britpop anthem with jangly guitars, catchy hooks, and witty lyrics about working-class life in modern Britain, featuring a driving rhythm section and melodic vocal harmonies",
"instrumental": false,
"vocal_language": "en"
}
@@ -0,0 +1,5 @@
{
"description": "Raw garage rock track with fuzzy distorted guitars, primal drumming, and aggressive vocals delivering rebellious lyrics about teenage angst and urban decay",
"instrumental": false,
"vocal_language": "en"
}
@@ -0,0 +1,5 @@
{
"description": "Psychedelic rock journey with swirling organ, phased guitars, and trippy effects, featuring dreamy vocals singing about cosmic exploration and altered consciousness",
"instrumental": false,
"vocal_language": "en"
}
@@ -0,0 +1,5 @@
{
"description": "Energetic surf rock instrumental with reverb-drenched twangy guitars, driving drums, and a catchy melody evoking sun-soaked beaches and crashing waves",
"instrumental": true,
"vocal_language": "en"
}
@@ -0,0 +1,5 @@
{
"description": "Flamboyant glam rock anthem with stomping beats, glittery guitar riffs, and theatrical vocals celebrating self-expression and rock and roll excess",
"instrumental": false,
"vocal_language": "en"
}
@@ -0,0 +1,5 @@
{
"description": "Experimental art rock composition with unconventional song structures, avant-garde instrumentation, and introspective vocals exploring themes of identity and alienation",
"instrumental": false,
"vocal_language": "en"
}
@@ -0,0 +1,5 @@
{
"description": "Complex math rock instrumental with intricate time signatures, angular guitar riffs, and precise polyrhythmic drumming creating a technically demanding sonic puzzle",
"instrumental": true,
"vocal_language": "en"
}
@@ -0,0 +1,5 @@
{
"description": "Abrasive noise rock track with dissonant guitar feedback, pounding bass, and shouted vocals expressing frustration and chaos in an industrial urban landscape",
"instrumental": false,
"vocal_language": "en"
}
@@ -0,0 +1,5 @@
{
"description": "Heavy stoner rock groove with thick fuzzy riffs, slow hypnotic tempo, and hazy vocals singing about desert highways and transcendental experiences",
"instrumental": false,
"vocal_language": "en"
}
@@ -0,0 +1,5 @@
{
"description": "energetic EDM festival anthem with heavy bass drops and euphoric synth leads",
"instrumental": false,
"vocal_language": "en"
}
@@ -0,0 +1,5 @@
{
"description": "Soulful southern rock ballad with slide guitar, Hammond organ, and heartfelt vocals telling stories of small-town life and lost love in the American South",
"instrumental": false,
"vocal_language": "en"
}
@@ -0,0 +1,5 @@
{
"description": "Anthemic heartland rock song with ringing guitars, driving rhythm, and earnest vocals celebrating blue-collar values and the American working-class spirit",
"instrumental": false,
"vocal_language": "en"
}
@@ -0,0 +1,5 @@
{
"description": "Infectious power pop track with crunchy guitars, tight harmonies, and an irresistible chorus about summer romance and youthful optimism",
"instrumental": false,
"vocal_language": "en"
}
@@ -0,0 +1,5 @@
{
"description": "Shimmering jangle pop tune with chiming arpeggiated guitars, upbeat tempo, and breezy vocals singing about nostalgic memories and bittersweet longing",
"instrumental": false,
"vocal_language": "en"
}
@@ -0,0 +1,5 @@
{
"description": "Ornate baroque pop arrangement with harpsichord, string quartet, and lush orchestration, featuring elegant vocals weaving tales of aristocratic romance and melancholy",
"instrumental": false,
"vocal_language": "en"
}
@@ -0,0 +1,5 @@
{
"description": "Sophisticated chamber pop piece with piano, woodwinds, and sweeping strings, featuring intimate vocals exploring themes of existential wonder and quiet introspection",
"instrumental": false,
"vocal_language": "en"
}
@@ -0,0 +1,5 @@
{
"description": "Aggressive dubstep track with heavy wobble bass, distorted synths, and intense drops that shake the speakers",
"instrumental": false,
"vocal_language": "en"
}
@@ -0,0 +1,5 @@
{
"description": "High-energy drum and bass anthem with rolling breakbeats, deep sub-bass, and euphoric vocal chops",
"instrumental": false,
"vocal_language": "en"
}
@@ -0,0 +1,5 @@
{
"description": "Classic house music with four-on-the-floor beats, soulful piano chords, and uplifting female vocals",
"instrumental": false,
"vocal_language": "en"
}
@@ -0,0 +1,5 @@
{
"description": "Dark industrial techno with pounding kicks, hypnotic synth loops, and relentless mechanical rhythms",
"instrumental": true,
"vocal_language": "en"
}
@@ -0,0 +1,5 @@
{
"description": "smooth R&B love song with soulful vocals and silky piano chords",
"instrumental": false,
"vocal_language": "en"
}
@@ -0,0 +1,5 @@
{
"description": "Uplifting trance with soaring melodies, ethereal pads, and emotional breakdowns that build to euphoric climaxes",
"instrumental": false,
"vocal_language": "en"
}
@@ -0,0 +1,5 @@
{
"description": "Hardstyle banger with distorted kicks, reverse bass, and anthemic vocals that ignite the crowd",
"instrumental": false,
"vocal_language": "en"
}
@@ -0,0 +1,5 @@
{
"description": "Colorful future bass with lush synth chords, pitched vocal samples, and emotional melodic drops",
"instrumental": false,
"vocal_language": "en"
}
@@ -0,0 +1,5 @@
{
"description": "Sunny tropical house with steel drums, marimba melodies, and breezy summer vocals",
"instrumental": false,
"vocal_language": "en"
}
@@ -0,0 +1,5 @@
{
"description": "Smooth deep house groove with warm basslines, subtle percussion, and hypnotic late-night atmosphere",
"instrumental": false,
"vocal_language": "en"
}
@@ -0,0 +1,5 @@
{
"description": "Epic progressive house journey with layered synths, gradual builds, and massive festival-ready drops",
"instrumental": false,
"vocal_language": "en"
}
@@ -0,0 +1,5 @@
{
"description": "Vintage electro swing with jazzy brass samples, swinging rhythms, and playful retro vocals",
"instrumental": false,
"vocal_language": "en"
}
@@ -0,0 +1,5 @@
{
"description": "Funky breakbeat track with chopped drum breaks, funky basslines, and energetic vocal hooks",
"instrumental": false,
"vocal_language": "en"
}
@@ -0,0 +1,5 @@
{
"description": "Chilled downtempo with ambient textures, slow grooves, and dreamy atmospheric soundscapes",
"instrumental": true,
"vocal_language": "en"
}
@@ -0,0 +1,5 @@
{
"description": "Experimental IDM with complex polyrhythms, glitchy textures, and intricate sound design",
"instrumental": false,
"vocal_language": "en"
}
@@ -0,0 +1,5 @@
{
"description": "uplifting country road trip anthem with acoustic guitar and harmonica",
"instrumental": false,
"vocal_language": "en"
}
@@ -0,0 +1,5 @@
{
"description": "Chaotic glitch hop with stuttering beats, digital artifacts, and fragmented vocal samples",
"instrumental": false,
"vocal_language": "en"
}
@@ -0,0 +1,5 @@
{
"description": "轻快的国风电子舞曲,融合古筝与现代节拍",
"instrumental": false,
"vocal_language": "zh"
}
@@ -0,0 +1,5 @@
{
"description": "深情的粤语情歌,带有港式复古风情",
"instrumental": false,
"vocal_language": "zh"
}
@@ -0,0 +1,5 @@
{
"description": "热血沸腾的电竞主题曲,激昂的电子音效",
"instrumental": false,
"vocal_language": "zh"
}
@@ -0,0 +1,5 @@
{
"description": "空灵的禅意音乐,适合瑜伽冥想",
"instrumental": true,
"vocal_language": "zh"
}
@@ -0,0 +1,5 @@
{
"description": "欢快的儿童歌曲,充满童真童趣",
"instrumental": false,
"vocal_language": "zh"
}
@@ -0,0 +1,5 @@
{
"description": "激情四射的摇滚现场版,吉他solo燃爆全场",
"instrumental": false,
"vocal_language": "zh"
}
@@ -0,0 +1,5 @@
{
"description": "温馨的家庭主题曲,充满爱与温暖",
"instrumental": false,
"vocal_language": "zh"
}
@@ -0,0 +1,5 @@
{
"description": "神秘的武侠风格配乐,刀光剑影江湖梦",
"instrumental": true,
"vocal_language": "zh"
}
@@ -0,0 +1,5 @@
{
"description": "动感的健身房音乐,节奏强劲充满力量",
"instrumental": false,
"vocal_language": "zh"
}
@@ -0,0 +1,5 @@
{
"description": "dark trap beat with haunting melodies and 808 bass",
"instrumental": false,
"vocal_language": "en"
}
@@ -0,0 +1,5 @@
{
"description": "古典戏曲风格流行歌曲,京剧元素与现代编曲融合",
"instrumental": false,
"vocal_language": "zh"
}
@@ -0,0 +1,5 @@
{
"description": "浪漫的校园民谣,青春回忆满满",
"instrumental": false,
"vocal_language": "zh"
}
@@ -0,0 +1,5 @@
{
"description": "悠扬的草原风情歌曲,马头琴与长调交织",
"instrumental": false,
"vocal_language": "zh"
}
@@ -0,0 +1,5 @@
{
"description": "时尚的都市R&B,慵懒性感的夜生活氛围",
"instrumental": false,
"vocal_language": "zh"
}
@@ -0,0 +1,5 @@
{
"description": "古风仙侠主题曲,飘逸空灵如临仙境",
"instrumental": false,
"vocal_language": "zh"
}
@@ -0,0 +1,5 @@
{
"description": "励志的毕业季歌曲,告别与祝福",
"instrumental": false,
"vocal_language": "zh"
}
@@ -0,0 +1,5 @@
{
"description": "深沉的蓝调爵士,午夜酒吧的忧郁情调",
"instrumental": false,
"vocal_language": "zh"
}
@@ -0,0 +1,5 @@
{
"description": "欢乐的新年贺岁歌曲,喜庆热闹迎新春",
"instrumental": false,
"vocal_language": "zh"
}
@@ -0,0 +1,5 @@
{
"description": "唯美的古典钢琴曲,如诗如画的江南水乡",
"instrumental": true,
"vocal_language": "zh"
}
@@ -0,0 +1,5 @@
{
"description": "热情的拉丁风格华语歌曲,异域风情与中文歌词碰撞",
"instrumental": false,
"vocal_language": "zh"
}
@@ -0,0 +1,5 @@
{
"description": "groovy funk jam with slap bass and wah-wah guitar riffs",
"instrumental": false,
"vocal_language": "en"
}
@@ -0,0 +1,5 @@
{
"description": "磅礴大气的史诗级电影配乐,气势恢宏震撼人心",
"instrumental": false,
"vocal_language": "zh"
}
@@ -0,0 +1,5 @@
{
"description": "疾走感のあるアニソンロック、熱いギターリフとパワフルなボーカル",
"instrumental": false,
"vocal_language": "ja"
}

Some files were not shown because too many files have changed in this diff Show More