internal/autogen/audiocpp_test.go
Rama main · réplica de lectura del repositorio original.
package autogen
import (
"os"
"path/filepath"
"reflect"
"strings"
"testing"
)
// audioCppSettings is a fleet with one installed audio.cpp build of the given
// variant, plus whatever other backend rows the case needs.
func audioCppSettings(variant string, extra ...BackendEntry) Settings {
rows := []BackendEntry{{
ID: "managed-audiocpp-server", Kind: "audiocpp", Name: "audio.cpp",
Path: "/backends/audiocpp_server", Managed: true, Variant: variant, Default: true,
}}
return Settings{Backends: append(rows, extra...), TtlSec: 600}
}
func audioCppRow(path string) GgufRow {
return GgufRow{FullPath: path, FileName: path[strings.LastIndexByte(path, '/')+1:], SizeGB: 0.2}
}
func TestIsAudioCppModel(t *testing.T) {
cases := []struct {
name string
meta Metadata
want bool
}{
{"arch", Metadata{Architecture: "audiocpp"}, true},
{"family only", Metadata{AudioFamily: "moss_tts_nano"}, true},
{"chat llm", Metadata{Architecture: "qwen35"}, false},
{"qwentts talker", Metadata{Architecture: "qwen3-tts"}, false},
{"kokoro", Metadata{Architecture: "kokoro"}, false},
}
for _, c := range cases {
if got := IsAudioCppModel(c.meta); got != c.want {
t.Errorf("%s: IsAudioCppModel = %v, want %v", c.name, got, c.want)
}
}
}
func TestAudioCppTask(t *testing.T) {
cases := map[string]string{
"moss_tts_nano": "tts",
"qwen3_asr": "asr",
"nemotron_asr": "asr",
"echo_tts": "tts", // clone-only is still speech synthesis
"firered_audio": "tts", // dual-task: one config entry, tts wins
"ace_step": "gen", // music
"htdemucs": "sep", // separation
"sheetsage2": "midi", // score transcription
"seed_vc": "", // voice conversion
"not_a_family": "",
}
for family, want := range cases {
if got := audioCppTask(family); got != want {
t.Errorf("audioCppTask(%q) = %q, want %q", family, got, want)
}
}
if audioCppKnownFamily("not_a_family") {
t.Error("audioCppKnownFamily(not_a_family) = true")
}
if !audioCppKnownFamily("ace_step") {
t.Error("audioCppKnownFamily(ace_step) = false, want true")
}
}
func TestEmitAudioCppModel_TTS(t *testing.T) {
s := audioCppSettings("vulkan")
row := audioCppRow("/models/speech/moss-tts-nano-100m-q8_0.gguf")
meta := Metadata{Architecture: "audiocpp", AudioFamily: "moss_tts_nano"}
var b strings.Builder
var emitted []string
emitAudioCppModel(&b, s, row, &Override{}, "moss-tts-nano-100m-q8_0", meta, &emitted)
out := b.String()
for _, want := range []string{
"/backends/audiocpp_server",
"--port ${PORT}",
"--backend vulkan",
"--max-loaded-models 1",
"--voice-dir /models/speech/voices",
" audiocpp:",
` family: "moss_tts_nano"`,
` path: "/models/speech/moss-tts-nano-100m-q8_0.gguf"`,
` task: "tts"`,
"concurrencyLimit: 1",
"checkEndpoint: /health",
"in: [text]",
"out: [audio]",
"voiceClone: true",
} {
if !strings.Contains(out, want) {
t.Errorf("emitted YAML missing %q:\n%s", want, out)
}
}
// There is no --model flag on audiocpp_server; naming the gguf on argv would
// be rejected outright.
if strings.Contains(out, "--model ") {
t.Errorf("emitted a --model flag, which audiocpp_server does not accept:\n%s", out)
}
if len(emitted) != 1 {
t.Fatalf("emitted = %v, want 1 entry", emitted)
}
}
func TestEmitAudioCppModel_ASR(t *testing.T) {
s := audioCppSettings("cuda")
row := audioCppRow("/models/speech/qwen3-asr-1.7b-q8_0.gguf")
meta := Metadata{Architecture: "audiocpp", AudioFamily: "qwen3_asr"}
var b strings.Builder
var emitted []string
emitAudioCppModel(&b, s, row, &Override{}, "qwen3-asr-1.7b-q8_0", meta, &emitted)
out := b.String()
for _, want := range []string{"--backend cuda", `task: "asr"`, "in: [audio]", "out: [text]"} {
if !strings.Contains(out, want) {
t.Errorf("emitted YAML missing %q:\n%s", want, out)
}
}
// An ASR model has no speech surface to clone a voice for.
if strings.Contains(out, "voiceClone") {
t.Errorf("ASR entry advertises voiceClone:\n%s", out)
}
if len(emitted) != 1 {
t.Fatalf("emitted = %v, want 1 entry", emitted)
}
}
// A family we know about but do not serve (voice conversion, codecs, alignment)
// must not reach the config at all: no other engine here could serve it either,
// and an entry in the Speech tab would fail every request.
func TestEmitAudioCppModel_UnservedFamiliesAreSkipped(t *testing.T) {
cases := []struct {
name string
family string
want string
}{
{"voice conversion", "seed_vc", "voice-conversion"},
{"unknown", "brand_new_tts", "unknown audio.cpp family"},
{"no family id", "", "no audiocpp.model_spec.family"},
}
for _, c := range cases {
s := audioCppSettings("vulkan")
row := audioCppRow("/models/speech/x-q8_0.gguf")
var b strings.Builder
var emitted []string
emitAudioCppModel(&b, s, row, &Override{}, "x-q8_0", Metadata{Architecture: "audiocpp", AudioFamily: c.family}, &emitted)
out := b.String()
if len(emitted) != 0 {
t.Errorf("%s: emitted = %v, want none", c.name, emitted)
}
if !strings.Contains(out, "# SKIPPED") || !strings.Contains(out, c.want) {
t.Errorf("%s: want a SKIPPED comment mentioning %q, got:\n%s", c.name, c.want, out)
}
if strings.Contains(out, "cmd:") {
t.Errorf("%s: emitted a launch command for an unserved family:\n%s", c.name, out)
}
}
}
// A hand-entered row records no build variant, so no --backend can be derived.
// audio.cpp's own default is CUDA, which is the wrong guess on most machines, so
// the config has to say so rather than silently emit nothing.
func TestEmitAudioCppModel_NoVariantWarns(t *testing.T) {
s := Settings{TtlSec: 600, Backends: []BackendEntry{{
ID: "hand", Kind: "audiocpp", Name: "audio.cpp", Path: "/opt/audiocpp_server", Default: true,
}}}
var b strings.Builder
var emitted []string
emitAudioCppModel(&b, s, audioCppRow("/models/speech/supertonic-3-q8_0.gguf"), &Override{},
"supertonic-3-q8_0", Metadata{Architecture: "audiocpp", AudioFamily: "supertonic"}, &emitted)
out := b.String()
if strings.Contains(out, "\n --backend ") {
t.Errorf("guessed a --backend for a row with no variant:\n%s", out)
}
if !strings.Contains(out, "# NOTE:") {
t.Errorf("no note about the cuda default:\n%s", out)
}
}
// With no audio.cpp build registered the entry is still emitted (so the model is
// visible and starts working the moment one is installed), but it says so.
func TestEmitAudioCppModel_NoBackendRegistered(t *testing.T) {
var b strings.Builder
var emitted []string
emitAudioCppModel(&b, Settings{TtlSec: 600}, audioCppRow("/models/speech/vibevoice-1.5b-q8_0.gguf"),
&Override{}, "vibevoice-1.5b-q8_0", Metadata{Architecture: "audiocpp", AudioFamily: "vibevoice"}, &emitted)
out := b.String()
if !strings.Contains(out, "# WARNING: no audio.cpp backend registered") {
t.Errorf("no warning about the missing backend:\n%s", out)
}
if len(emitted) != 1 {
t.Fatalf("emitted = %v, want the model to still be listed", emitted)
}
}
// audio.cpp serves the tts class, so it can be the class default -- but it reads
// neither legacy engine's weights. A Kokoro gguf on a box where audio.cpp owns
// the tts default must still resolve to TTS.cpp, not to an audiocpp_server
// command built around a --model-path it does not accept.
func TestTTSBackend_IgnoresAudioCppRow(t *testing.T) {
s := Settings{TtsServerExe: "tts-server", Backends: []BackendEntry{{
ID: "managed-audiocpp-server", Kind: "audiocpp", Path: "/backends/audiocpp_server", Default: true,
}}}
row := GgufRow{FullPath: "/models/tts/Kokoro_espeak_f16.gguf", FileName: "Kokoro_espeak_f16.gguf"}
kind, exe := ttsBackend(s, row, &Override{}, Metadata{Architecture: "kokoro"})
if kind != ttsKindTTSCpp {
t.Errorf("kind = %q, want %q", kind, ttsKindTTSCpp)
}
if strings.Contains(exe, "audiocpp") {
t.Errorf("exe = %q, want the legacy tts exe", exe)
}
}
// audio.cpp is the first kind serving two classes at once, which is what the
// registry's "an install never steals a populated class" rule has to reason
// about: arriving on a box that already has an ASR engine must not hand it the
// tts auto-pick either, because the two travel together in one row.
func TestAudioCppIsMultiClass(t *testing.T) {
for _, class := range []string{"tts", "asr"} {
if !KindServesClass("audiocpp", class) {
t.Errorf("KindServesClass(audiocpp, %q) = false", class)
}
}
if got := KindClasses("audiocpp"); len(got) != 2 {
t.Errorf("KindClasses(audiocpp) = %v, want two classes", got)
}
if !ClassTaken([]BackendEntry{{ID: "parakeet", Kind: "asr"}}, "audiocpp") {
t.Error("ClassTaken = false with an asr row present, want true")
}
if ClassTaken([]BackendEntry{{ID: "llama", Kind: "llama"}}, "audiocpp") {
t.Error("ClassTaken = true against an llm-only registry, want false")
}
}
// The same rule on the other class this kind serves: a Parakeet gguf on a box
// where audio.cpp owns the asr default must still launch parakeet-server, not
// audiocpp_server wearing parakeet's argv.
func TestASRExe_IgnoresAudioCppRow(t *testing.T) {
s := Settings{AsrServerExe: "parakeet-server", Backends: []BackendEntry{{
ID: "managed-audiocpp-server", Kind: "audiocpp", Path: "/backends/audiocpp_server", Default: true,
}}}
if exe := asrExe(s, &Override{}); exe != "parakeet-server" {
t.Errorf("exe = %q, want the legacy asr exe", exe)
}
// A real ASR backend row still wins, as it does for every other class.
s.Backends = append(s.Backends, BackendEntry{ID: "pk", Kind: "parakeet", Path: "/backends/parakeet", Default: true})
if exe := asrExe(s, &Override{}); exe != "/backends/parakeet" {
t.Errorf("exe = %q, want the registered parakeet row", exe)
}
}
// A pin at another engine's row must be a no-op, not a broken launch: audio.cpp
// weights are its own format, so there is nothing for TTS.cpp to run here, and
// falling back to the bare "audiocpp_server" name (not on PATH) while warning
// that no backend is installed would be the worst of both.
func TestAudioCppBackend_IgnoresCrossEnginePin(t *testing.T) {
s := audioCppSettings("vulkan", BackendEntry{
ID: "ttscpp-row", Kind: "ttscpp", Name: "tts.cpp", Path: "/backends/tts-server",
})
be := audioCppBackend(s, &Override{Backend: "ttscpp-row"}, "tts")
if be.Exe != "/backends/audiocpp_server" {
t.Errorf("exe = %q, want the installed audio.cpp build", be.Exe)
}
// A pin at a specific audio.cpp BUILD still wins, which is what pinning is for.
s.Backends = append(s.Backends, BackendEntry{
ID: "build-audiocpp-cuda", Kind: "audiocpp", Path: "/backends/cuda/audiocpp_server",
Managed: true, Build: true, Variant: "cuda",
})
be = audioCppBackend(s, &Override{Backend: "build-audiocpp-cuda"}, "tts")
if be.Exe != "/backends/cuda/audiocpp_server" {
t.Errorf("exe = %q, want the pinned cuda build", be.Exe)
}
if got := audioCppFlavour(s, be.ID); got != "cuda" {
t.Errorf("flavour = %q, want cuda from the pinned build row", got)
}
}
func TestEmitAudioCppModel_MusicGen(t *testing.T) {
s := audioCppSettings("vulkan")
row := audioCppRow("/models/music/acestep-v15-turbo-bf16.gguf")
meta := Metadata{Architecture: "audiocpp", AudioFamily: "ace_step"}
var b strings.Builder
var emitted []string
emitAudioCppModel(&b, s, row, &Override{}, "acestep-v15-turbo-bf16", meta, &emitted)
out := b.String()
for _, want := range []string{`task: "gen"`, `family: "ace_step"`, "music: true", "musicEdit: true",
// Without mem_saver a seed stops reproducing after a longer render.
"sessionOptions:", `ace_step.mem_saver: "true"`} {
if !strings.Contains(out, want) {
t.Errorf("emitted YAML missing %q:\n%s", want, out)
}
}
if strings.Contains(out, "musicScore") {
t.Errorf("ACE-Step entry advertises a score:\n%s", out)
}
// A music generator has no speech surface: text->audio would advertise
// audio_speech, and voice cloning does not apply.
for _, bad := range []string{"in: [text]", "out: [audio]", "voiceClone"} {
if strings.Contains(out, bad) {
t.Errorf("music entry advertises %q:\n%s", bad, out)
}
}
if len(emitted) != 1 {
t.Fatalf("emitted = %v, want 1 entry", emitted)
}
}
func TestEmitAudioCppModel_Separation(t *testing.T) {
s := audioCppSettings("vulkan")
row := audioCppRow("/models/music/htdemucs-f16.gguf")
meta := Metadata{Architecture: "audiocpp", AudioFamily: "htdemucs"}
var b strings.Builder
var emitted []string
emitAudioCppModel(&b, s, row, &Override{}, "htdemucs-f16", meta, &emitted)
out := b.String()
if !strings.Contains(out, `task: "sep"`) || !strings.Contains(out, "separation: true") {
t.Errorf("separation entry missing task/capability:\n%s", out)
}
if strings.Contains(out, "mem_saver") {
t.Errorf("separation entry carries an ace_step option:\n%s", out)
}
if strings.Contains(out, "music: true") {
t.Errorf("separation entry advertises music:\n%s", out)
}
if strings.Contains(out, "in: [") {
t.Errorf("separation entry declares speech in/out:\n%s", out)
}
if len(emitted) != 1 {
t.Fatalf("emitted = %v, want 1 entry", emitted)
}
}
// writeMiniMaxPackage lays out a MiniMax Music 3 package directory: the shared
// vocoder carries the family id in its header, the other components carry none.
// Each file in files is written as a non-empty placeholder, except the shared
// vocoder and condition encoder, which are real GGUF headers.
func writeMiniMaxPackage(t *testing.T, dir string, files ...string) {
t.Helper()
if err := os.MkdirAll(dir, 0o755); err != nil {
t.Fatal(err)
}
family := [][2]any{
{"general.architecture", "audiocpp"},
{"audiocpp.model_spec.family", "minimax_music3"},
}
for _, f := range files {
p := filepath.Join(dir, f)
if f == "vocoder.gguf" || f == "condition_encoder.gguf" {
writeGgufKVs(t, p, family)
continue
}
if err := os.WriteFile(p, []byte("placeholder-weights"), 0o644); err != nil {
t.Fatal(err)
}
}
}
// miniMaxRow is a GgufRow for a file inside a package directory, with FileName
// set to the base name (audioCppRow splits on "/" only, which a Windows
// filepath.Join path does not contain).
func miniMaxRow(path string) GgufRow {
row := audioCppRow(filepath.ToSlash(path))
row.FileName = filepath.Base(path)
return row
}
func TestEmitAudioCppModel_MiniMaxDirectory(t *testing.T) {
dir := filepath.Join(t.TempDir(), "MiniMax-Music-3-GGUF")
writeMiniMaxPackage(t, dir,
"vocoder.gguf", "condition_encoder.gguf",
"language_model_q8_0.gguf", "transformer_q8_0.gguf", "rvq_depth_decoder_q8_0.gguf",
"language_model_q4_0.gguf", // no transformer_q4_0: q4_0 must not be emitted
)
s := audioCppSettings("vulkan")
row := miniMaxRow(filepath.Join(dir, "vocoder.gguf"))
meta := Metadata{Architecture: "audiocpp", AudioFamily: "minimax_music3"}
var b strings.Builder
var emitted []string
emitAudioCppModel(&b, s, row, &Override{}, "vocoder", meta, &emitted)
out := b.String()
for _, want := range []string{
`"minimax-music-3-q8_0":`,
`minimax_music3.language_model_gguf: "language_model_q8_0.gguf"`,
`minimax_music3.flow_transformer_gguf: "transformer_q8_0.gguf"`,
`minimax_music3.rvq_depth_decoder_gguf: "rvq_depth_decoder_q8_0.gguf"`,
// Vulkan renders the q8_0 components as hiss unless they load as f16.
`minimax_music3.weight_type: "f16"`,
"sessionOptions:",
"music: true",
`path: "` + filepath.ToSlash(dir) + `"`,
} {
if !strings.Contains(out, want) {
t.Errorf("emitted YAML missing %q:\n%s", want, out)
}
}
if strings.Contains(out, "minimax-music-3-q4_0") {
t.Errorf("emitted q4_0 with no transformer_q4_0 on disk:\n%s", out)
}
// MiniMax has no edit routes.
if strings.Contains(out, "musicEdit") {
t.Errorf("MiniMax entry advertises musicEdit:\n%s", out)
}
if want := []string{"minimax-music-3-q8_0"}; !reflect.DeepEqual(emitted, want) {
t.Fatalf("emitted = %v, want %v", emitted, want)
}
}
// The f16 conversion is a Vulkan workaround: other backends, and the package's
// own bf16 quant (stored as f16 on Vulkan already), load native.
func TestEmitAudioCppModel_MiniMaxWeightTypeOnlyOnVulkanQuants(t *testing.T) {
dir := filepath.Join(t.TempDir(), "MiniMax-Music-3-GGUF")
writeMiniMaxPackage(t, dir,
"vocoder.gguf", "condition_encoder.gguf",
"language_model_bf16.gguf", "transformer_bf16.gguf", "rvq_depth_decoder_bf16.gguf",
"language_model_q8_0.gguf", "transformer_q8_0.gguf", "rvq_depth_decoder_q8_0.gguf",
)
meta := Metadata{Architecture: "audiocpp", AudioFamily: "minimax_music3"}
row := miniMaxRow(filepath.Join(dir, "vocoder.gguf"))
var b strings.Builder
var emitted []string
emitAudioCppModel(&b, audioCppSettings("cuda"), row, &Override{}, "vocoder", meta, &emitted)
if out := b.String(); strings.Contains(out, "weight_type") {
t.Errorf("CUDA entry carries weight_type:\n%s", out)
}
b.Reset()
emitted = nil
emitAudioCppModel(&b, audioCppSettings("vulkan"), row, &Override{}, "vocoder", meta, &emitted)
out := b.String()
if n := strings.Count(out, "weight_type"); n != 1 {
t.Errorf("weight_type appears %d times on Vulkan, want once (q8_0 only):\n%s", n, out)
}
bf16 := out[strings.Index(out, `"minimax-music-3-bf16":`):strings.Index(out, `"minimax-music-3-q8_0":`)]
if strings.Contains(bf16, "weight_type") {
t.Errorf("bf16 entry carries weight_type:\n%s", bf16)
}
}
// A pinned weight type beats the Vulkan rule both ways, and the editor's
// "Auto" label reports the pin-free rule.
func TestEmitAudioCppModel_MiniMaxWeightTypePin(t *testing.T) {
dir := filepath.Join(t.TempDir(), "MiniMax-Music-3-GGUF")
writeMiniMaxPackage(t, dir,
"vocoder.gguf", "condition_encoder.gguf",
"language_model_q8_0.gguf", "transformer_q8_0.gguf", "rvq_depth_decoder_q8_0.gguf",
)
meta := Metadata{Architecture: "audiocpp", AudioFamily: "minimax_music3"}
row := miniMaxRow(filepath.Join(dir, "vocoder.gguf"))
emit := func(flavour, pin string) string {
var b strings.Builder
var emitted []string
emitAudioCppModel(&b, audioCppSettings(flavour), row, &Override{AudioWeightType: pin}, "vocoder", meta, &emitted)
return b.String()
}
if out := emit("vulkan", AudioWeightNative); strings.Contains(out, "weight_type") {
t.Errorf("native pin on Vulkan still emits weight_type:\n%s", out)
}
if out := emit("cuda", AudioWeightF16); !strings.Contains(out, `minimax_music3.weight_type: "f16"`) {
t.Errorf("f16 pin on CUDA emits no weight_type:\n%s", out)
}
opts := map[string]string{"minimax_music3.language_model_gguf": "language_model_q8_0.gguf"}
pinned := &Override{AudioWeightType: AudioWeightNative}
if got := AudioCppWeightTypeAuto(audioCppSettings("vulkan"), pinned, "minimax_music3", opts); got != AudioWeightF16 {
t.Errorf("auto on Vulkan q8_0 = %q, want f16 (the pin must not leak into it)", got)
}
if got := AudioCppWeightTypeAuto(audioCppSettings("cuda"), nil, "minimax_music3", opts); got != AudioWeightNative {
t.Errorf("auto on CUDA = %q, want native", got)
}
if got := AudioCppWeightTypeAuto(audioCppSettings("vulkan"), nil, "ace_step", opts); got != "" {
t.Errorf("auto for a family with no choice = %q, want empty", got)
}
}
func TestEmitAudioCppModel_MiniMaxComponents(t *testing.T) {
dir := filepath.Join(t.TempDir(), "MiniMax-Music-3-GGUF")
writeMiniMaxPackage(t, dir,
"vocoder.gguf", "condition_encoder.gguf",
"language_model_q8_0.gguf", "transformer_q8_0.gguf", "rvq_depth_decoder_q8_0.gguf",
)
s := audioCppSettings("vulkan")
// A family-less per-quant component is explained by its package, not skipped.
var b strings.Builder
var emitted []string
emitAudioCppModel(&b, s, miniMaxRow(filepath.Join(dir, "transformer_q8_0.gguf")), &Override{},
"transformer_q8_0", Metadata{Architecture: "audiocpp"}, &emitted)
out := b.String()
if !strings.Contains(out, "component of the minimax_music3 package") {
t.Errorf("no package note for a family-less component:\n%s", out)
}
if strings.Contains(out, "SKIPPED") {
t.Errorf("family-less component of a known package was SKIPPED:\n%s", out)
}
if len(emitted) != 0 {
t.Errorf("emitted = %v for a component, want none", emitted)
}
// The shared condition encoder carries the family but is not the anchor.
b.Reset()
emitAudioCppModel(&b, s, miniMaxRow(filepath.Join(dir, "condition_encoder.gguf")), &Override{},
"condition_encoder", Metadata{Architecture: "audiocpp", AudioFamily: "minimax_music3"}, &emitted)
out = b.String()
if !strings.Contains(out, "shared component") {
t.Errorf("no shared-component note for condition_encoder.gguf:\n%s", out)
}
if len(emitted) != 0 {
t.Errorf("emitted = %v for the shared encoder, want none", emitted)
}
}
func TestEmitAudioCppModel_MiniMaxIncomplete(t *testing.T) {
dir := filepath.Join(t.TempDir(), "MiniMax-Music-3-GGUF")
writeMiniMaxPackage(t, dir, "vocoder.gguf", "language_model_q8_0.gguf")
s := audioCppSettings("vulkan")
var b strings.Builder
var emitted []string
emitAudioCppModel(&b, s, miniMaxRow(filepath.Join(dir, "vocoder.gguf")), &Override{},
"vocoder", Metadata{Architecture: "audiocpp", AudioFamily: "minimax_music3"}, &emitted)
out := b.String()
if !strings.Contains(out, "SKIPPED minimax_music3: no complete quant") {
t.Errorf("no skip note for a package with no complete quant:\n%s", out)
}
if len(emitted) != 0 {
t.Errorf("emitted = %v for an incomplete package, want none", emitted)
}
}
// YuE2 is a folder model: the main gguf carries the family and anchors the
// emit, the VAE beside it carries none and is a component, not a broken export.
func TestEmitAudioCppModel_Yue2Companion(t *testing.T) {
dir := filepath.Join(t.TempDir(), "Yue2-3B-GGUF")
if err := os.MkdirAll(dir, 0o755); err != nil {
t.Fatal(err)
}
writeGgufKVs(t, filepath.Join(dir, "yue2-3b-q8_0.gguf"), [][2]any{
{"general.architecture", "audiocpp"},
{"audiocpp.model_spec.family", "yue2"},
})
s := audioCppSettings("vulkan")
meta := Metadata{Architecture: "audiocpp", AudioFamily: "yue2"}
main := miniMaxRow(filepath.Join(dir, "yue2-3b-q8_0.gguf"))
// No VAE yet: nothing loadable, and the YAML names the missing file.
var b strings.Builder
var emitted []string
emitAudioCppModel(&b, s, main, &Override{}, "yue2-3b-q8_0", meta, &emitted)
if out := b.String(); !strings.Contains(out, "SKIPPED yue2-3b-q8_0: yue2 needs its companion yue2-vae-f16.gguf") || len(emitted) != 0 {
t.Fatalf("emitted = %v without a VAE:\n%s", emitted, out)
}
for _, f := range []string{"yue2-vae-f16.gguf", "yue2-vae-f32.gguf"} {
if err := os.WriteFile(filepath.Join(dir, f), []byte("placeholder-weights"), 0o644); err != nil {
t.Fatal(err)
}
}
b.Reset()
emitAudioCppModel(&b, s, main, &Override{}, "yue2-3b-q8_0", meta, &emitted)
out := b.String()
for _, want := range []string{
`"yue2-3b-q8_0":`,
`family: "yue2"`,
`task: "gen"`,
`path: "` + filepath.ToSlash(dir) + `"`,
`yue2.model_gguf: "yue2-3b-q8_0.gguf"`,
`yue2.vae_gguf: "yue2-vae-f16.gguf"`,
"music: true",
"musicScore: abc",
} {
if !strings.Contains(out, want) {
t.Errorf("emitted YAML missing %q:\n%s", want, out)
}
}
if strings.Contains(out, "musicEdit") {
t.Errorf("YuE2 entry advertises musicEdit:\n%s", out)
}
if want := []string{"yue2-3b-q8_0"}; !reflect.DeepEqual(emitted, want) {
t.Fatalf("emitted = %v, want %v", emitted, want)
}
}
// A full-precision YuE2 pairs the f32 VAE first, except on a Vulkan build,
// where the f32 decode needs one allocation over the device limit.
func TestEmitAudioCppModel_Yue2FullPrecisionVAE(t *testing.T) {
dir := filepath.Join(t.TempDir(), "Yue2-3B-GGUF")
if err := os.MkdirAll(dir, 0o755); err != nil {
t.Fatal(err)
}
for _, f := range []string{"yue2-3b-bf16.gguf", "yue2-3b-q8_0.gguf", "yue2-vae-f16.gguf", "yue2-vae-f32.gguf"} {
writeGgufKVs(t, filepath.Join(dir, f), [][2]any{
{"general.architecture", "audiocpp"},
{"audiocpp.model_spec.family", "yue2"},
})
}
meta := Metadata{Architecture: "audiocpp", AudioFamily: "yue2"}
cases := []struct{ variant, main, vae string }{
{"rocm", "yue2-3b-bf16.gguf", "yue2-vae-f32.gguf"},
{"cuda", "yue2-3b-bf16.gguf", "yue2-vae-f32.gguf"},
{"vulkan", "yue2-3b-bf16.gguf", "yue2-vae-f16.gguf"},
{"rocm", "yue2-3b-q8_0.gguf", "yue2-vae-f16.gguf"},
}
for _, c := range cases {
var b strings.Builder
var emitted []string
emitAudioCppModel(&b, audioCppSettings(c.variant), miniMaxRow(filepath.Join(dir, c.main)), &Override{}, "yue2", meta, &emitted)
if want := `yue2.vae_gguf: "` + c.vae + `"`; !strings.Contains(b.String(), want) {
t.Errorf("%s %s: want %s in:\n%s", c.variant, c.main, want, b.String())
}
}
}
func TestEmitAudioCppModel_Transcription(t *testing.T) {
s := audioCppSettings("vulkan")
row := audioCppRow("/models/music/sheetsage2-orig.gguf")
meta := Metadata{Architecture: "audiocpp", AudioFamily: "sheetsage2"}
var b strings.Builder
var emitted []string
emitAudioCppModel(&b, s, row, &Override{}, "sheetsage2-orig", meta, &emitted)
out := b.String()
if !strings.Contains(out, `task: "midi"`) || !strings.Contains(out, "musicTranscribe: abc") {
t.Errorf("transcription entry missing task/capability:\n%s", out)
}
for _, bad := range []string{"music: true", "separation: true", "in: ["} {
if strings.Contains(out, bad) {
t.Errorf("transcription entry carries %q:\n%s", bad, out)
}
}
if len(emitted) != 1 {
t.Fatalf("emitted = %v, want 1 entry", emitted)
}
}
// A ROCm build must launch with --backend hip: without a flag the server
// defaults to cuda, which an AMD build cannot open.
func TestAudioCppFlavour_Rocm(t *testing.T) {
for _, v := range []string{"rocm", "hip", "ROCm"} {
s := Settings{Backends: []BackendEntry{{ID: "a", Kind: "audiocpp", Variant: v}}}
if got := audioCppFlavour(s, "a"); got != "hip" {
t.Errorf("variant %q: flavour = %q, want hip", v, got)
}
}
}