Initial release
This commit is contained in:
@@ -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(¶ms);
|
||||
|
||||
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(®istry, 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() ? ®istry.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() ? ®istry.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, ¶ms);
|
||||
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;
|
||||
}
|
||||
Reference in New Issue
Block a user