diff --git a/README.md b/README.md index a11cc37..0792a40 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,8 @@ Bild- und Sprachausgabe. **Hermes und die Fach-MCPs laufen auf Unraid.** - Qwen3-TTS 1.7B auf der RTX 3060 mit Piper als CPU-Fallback - Whisper.cpp `large-v3-turbo` auf der CPU für lokale deutsche Spracherkennung - Live-Dashboard mit 21 Tagen Detailhistorie auf Port 8099 -- Dashboard-Umschaltung zwischen LLM-Betrieb und ACE-Step-Musikstudio +- Dashboard-Umschaltung zwischen LLM-Betrieb und ACE-Step-Musikstudio mit + persistenter `fspecii/ace-step-ui`-Bibliothek - Portainer CE als optionale Container-Ansicht auf Port 9443 - WireGuard-Gateway, Datenbackup und Athena-Operator - keine produktive Hermes-, OpenWebUI- oder portable Fach-MCP-Instanz @@ -121,6 +122,8 @@ Details, Installation, Prüfung und Rollback stehen in - Router: `http://192.168.1.212:8081/v1` - Athena-Dashboard: `http://192.168.1.212:8099` +- Musikstudio, Original UI (stabil): `http://192.168.1.212:7862` +- Musikstudio, Community UI (experimentell): `http://192.168.1.212:7861` Der Betriebsmodus lässt sich dort direkt umschalten. In Hermes funktionieren außerdem `/athena music`, `/athena llm` und `/athena status`; Details stehen in diff --git a/compose.yaml b/compose.yaml index 97919ad..3eabdf0 100644 --- a/compose.yaml +++ b/compose.yaml @@ -856,7 +856,8 @@ services: DASHBOARD_PORT: "8099" ROUTER_URL: http://router:8081 ROUTER_API_KEY: "${ROUTER_API_KEY:?ROUTER_API_KEY is required}" - MUSIC_UI_URL: "${MUSIC_UI_URL:-http://127.0.0.1:7861/}" + MUSIC_COMMUNITY_UI_URL: "${MUSIC_COMMUNITY_UI_URL:-http://192.168.1.212:7861/}" + MUSIC_ORIGINAL_UI_URL: "${MUSIC_ORIGINAL_UI_URL:-http://192.168.1.212:7862/}" HOST_PROC: /host/proc HOST_DATA: /host/data HOST_MODELS: /host/models diff --git a/docs/OPERATING_MODES.md b/docs/OPERATING_MODES.md index f917bd7..7de8904 100644 --- a/docs/OPERATING_MODES.md +++ b/docs/OPERATING_MODES.md @@ -12,8 +12,25 @@ wird persistent gespeichert und beim Verlassen des Musikmodus wieder geladen. ## Bedienung Im Athena-Dashboard stehen die Schaltflächen **LLM-Betrieb** und -**Musikstudio** bereit. Nach dem Start des Musikmodus öffnet **Studio öffnen** -die über den SSH-Tunnel bereitgestellte ACE-Step-Oberfläche. +**Musikstudio starten** bereit. Im Musikmodus werden zwei Oberflächen angeboten: + +- **Original UI · stabil** öffnet die zum laufenden ACE-Step-Image gehörende + Gradio-Oberfläche. Sie ist für Cover, Remix und erweiterte Workflows der + verbindliche Produktionspfad. +- **Community UI · experimentell** öffnet `fspecii/ace-step-ui`. Die + CPU-leichte React/Express-Anwendung hält Bibliothek, Playlists und + Einstellungen in `/data/music/ace-step-ui`. Ein noch nicht übernommener + Upstream-Kompatibilitätsfix für die aktuelle 72-Felder-Gradio-API ist lokal + zurückportiert; normale Generierung funktioniert, Cover und Remix gelten bis + zu eigenen Ende-zu-Ende-Tests weiterhin als experimentell. + +Die Community-Oberfläche ist im WireGuard-Netz unter +`http://192.168.1.212:7861`, die originale Gradio-Oberfläche unter +`http://192.168.1.212:7862` erreichbar. Beide Host-Ports bleiben zusätzlich +auf `127.0.0.1` gebunden und werden auf der Universitäts-Schnittstelle nicht +veröffentlicht. `ace-step-ui` ist reproduzierbar auf Commit +`a1fdf91829ec6f7b98844f80e323529cd155dbf2` fixiert und greift intern über das +Docker-Netz `mike-ai-music` auf `http://music-worker:7860` zu. Hermes benötigt dafür kein Plugin. Exakt eingegebene Steuerbefehle werden vom Router lokal beantwortet, auch wenn gerade kein LLM geladen ist: diff --git a/docs/SPECIALIZED_MODEL_ROADMAP.md b/docs/SPECIALIZED_MODEL_ROADMAP.md index 9a816ab..82d0c8e 100644 --- a/docs/SPECIALIZED_MODEL_ROADMAP.md +++ b/docs/SPECIALIZED_MODEL_ROADMAP.md @@ -8,7 +8,7 @@ im zentralen Register `TESTED_MODELS.md` dokumentiert wurde. | Prioritaet | Aufgabe | Kandidat | Geplanter Betrieb | Status | |---:|---|---|---|---| -| 1 | Musik erzeugen und bearbeiten | `ACE-Step 1.5 XL SFT` mit `acestep-5Hz-lm-1.7B` | exklusives On-Demand-Profil auf der RTX 5080; CPU-Offload; Qwen, Vision und TTS werden waehrenddessen entladen | **technisch bestanden; Klangabnahme und Integration offen** | +| 1 | Musik erzeugen und bearbeiten | `ACE-Step 1.5 XL SFT` mit `acestep-5Hz-lm-1.7B` | exklusives On-Demand-Profil auf der RTX 5080; CPU-Offload; Qwen, Vision und TTS werden waehrenddessen entladen | **integriert; Klangabnahme laeuft** | | 2 | Gesang und Instrumente trennen | BS-RoFormer Viperx 1297; alternativ MelBand-RoFormer, fuer Mehrspur `htdemucs_ft` | eigener Audio-Worker; GPU bevorzugt, CPU als langsamer Fallback | offen | | 3 | Voice Cloning | vorhandenes `Qwen3-TTS-12Hz-1.7B-Base` | bestehender TTS-Worker auf der RTX 3060; zunaechst den eingebauten 3-Sekunden-Klonpfad freilegen | offen | | 4 | Objekte lokalisieren und zaehlen | Grounding DINO oder RF-DETR | optionaler Vision-Worker; normales Erkennen bleibt beim vorhandenen Qwen-Vision-Projektor | offen | @@ -47,7 +47,14 @@ Erster Messlauf am 8. September 2026: Ein 30-Sekunden-Instrumental wurde in 15,39 Sekunden erzeugt (LM 8,00 s, DiT 7,39 s, MP3-Encoding 0,82 s). Die gemeldete maximale CUDA-Allokation lag bei 9,38 GiB. Es gab weder OOM noch CUDA-Fehler. Der technische Test ist damit bestanden; die subjektive -Klangabnahme erfolgt ueber die geoeffnete Gradio-Oberflaeche. +Die originale ACE-Step-Gradio-Oberflaeche ist der stabile Produktionspfad. Die +persistente `fspecii/ace-step-ui`-Oberflaeche bleibt bis zur Abnahme aller +Audio-zu-Audio-Modi experimentell. Der lokale Backport des offenen Upstream-PR +[#109](https://github.com/fspecii/ace-step-ui/pull/109) wurde gegen die 72 +Parameter des live veroeffentlichten `/generation_wrapper`-Schemas getestet. +Ein zehnsekündiger FLAC-Textauftrag lief am 8. September 2026 erfolgreich durch +UI, Express-Backend und Gradio-API und wurde in der persistenten Bibliothek +gespeichert; dieser Test belegt Cover und Remix ausdrücklich noch nicht. Offizielle Referenzen: [ACE-Step 1.5](https://github.com/ace-step/ACE-Step-1.5) und [REST-API](https://github.com/ace-step/ACE-Step-1.5/blob/main/docs/en/API.md). diff --git a/experiments/acestep15-xl-sft/README.md b/experiments/acestep15-xl-sft/README.md index 8ecc7df..a57d8d9 100644 --- a/experiments/acestep15-xl-sft/README.md +++ b/experiments/acestep15-xl-sft/README.md @@ -1,9 +1,9 @@ # ACE-Step 1.5 XL SFT: isolierter Athena-Test -Dieser Compose-Dienst ist absichtlich noch nicht Teil des produktiven Stacks. -Er startet nicht automatisch, bindet die Testoberflaeche nur an localhost und -kann mitsamt seinem Container entfernt werden, ohne Router oder Hermes zu -veraendern. Gewichte und Ergebnisse liegen getrennt unter `/data`. +Der GPU-Worker startet nur im Musikmodus und bindet seine rohe +Gradio-Oberflaeche nur an localhost. Die separate `fspecii/ace-step-ui` +bleibt als leichte React/Express-Oberflaeche aktiv; ihre SQLite-Datenbank, +Bibliothek und Uploads liegen persistent unter `/data/music/ace-step-ui`. Das offizielle Image ist auf den am 8. September 2026 geladenen Digest `sha256:95652cd780c78a1b1a7f6f0335530430f0ae53d96c7c12d59f9f39fa23d38567` @@ -30,6 +30,23 @@ bleiben bewusst vorgenommene Aenderungen wieder im jeweiligen Browser erhalten. Beim Wechsel des gepinnten Image-Digests muessen die Overrides gegen die neue Upstream-Fassung geprueft werden. +## Zwei Musikoberflaechen + +Die originale Gradio-Oberflaeche aus demselben ACE-Step-Image ist der stabile +Produktionspfad fuer Simple, Custom, Cover, Remix und Repaint. Sie ist im +WireGuard-Netz unter `http://192.168.1.212:7862` erreichbar. + +`music-ui` baut [fspecii/ace-step-ui](https://github.com/fspecii/ace-step-ui) +reproduzierbar von Commit `a1fdf91829ec6f7b98844f80e323529cd155dbf2`. +Ein lokaler Backport des noch offenen Upstream-PR +[#109](https://github.com/fspecii/ace-step-ui/pull/109) gleicht das Frontend an +das aktuelle 72-Felder-Gradio-Schema an. Athena-spezifisch sind nur die +persistente Ablage, die XL-SFT-Anzeige und die gemeldeten Laufzeitlimits. +Schlaegt bei einer spaeteren Upstream-Fassung ein Patch-Anker fehl, bricht der +Image-Build ab. Die Community-UI unter `http://192.168.1.212:7861` ist bis zu +vollstaendigen Ende-zu-Ende-Tests von Cover und Remix als experimentell +gekennzeichnet. + ## Start Vor dem Start muessen das aktive llama.cpp-Profil und Qwen3-TTS beendet sein. @@ -37,24 +54,28 @@ Die RTX 5080 wird ueber ihre UUID exklusiv an den Container uebergeben. ```bash export ACESTEP_GPU_UUID="GPU-..." +export ACESTEP_UI_JWT_SECRET="$(openssl rand -hex 32)" +docker compose up -d music-ui docker compose --profile music-test up -d music-worker docker compose logs -f music-worker ``` -Die lokale Oberflaeche kann vom Mac aus ueber einen SSH-Tunnel erreicht werden: +Alternativ zum WireGuard-Zugriff lassen sich beide Oberflaechen per SSH-Tunnel +erreichen: ```bash -ssh -L 7861:127.0.0.1:7861 root@athena.scc.kit.edu +ssh -L 7861:127.0.0.1:7861 -L 7862:127.0.0.1:7862 root@athena.scc.kit.edu ``` -Danach im Browser `http://127.0.0.1:7861` aufrufen. +`ace-step-ui` spricht den Worker ausschliesslich ueber das interne Docker-Netz +an. Dafuer startet ACE-Step mit aktivierten, benannten API-Endpunkten +(`--enable-api`). ## Beenden ```bash -docker compose --profile music-test down +docker compose --profile music-test stop music-worker ``` -Der Befehl entfernt nur den Testcontainer und das Testnetz. Caches, -Modellgewichte und Ausgaben bleiben fuer reproduzierbare Messungen erhalten. -Sie werden erst nach dokumentierter Ablehnung gezielt geloescht. +Der Befehl stoppt nur den GPU-Worker. Die Musikoberflaeche, ihre Bibliothek, +Caches, Modellgewichte und Ausgaben bleiben erhalten. diff --git a/experiments/acestep15-xl-sft/ace-step-ui/Dockerfile b/experiments/acestep15-xl-sft/ace-step-ui/Dockerfile new file mode 100644 index 0000000..36af8d5 --- /dev/null +++ b/experiments/acestep15-xl-sft/ace-step-ui/Dockerfile @@ -0,0 +1,38 @@ +FROM node:22-bookworm AS build + +ARG ACE_STEP_UI_COMMIT +RUN test -n "$ACE_STEP_UI_COMMIT" +RUN apt-get update \ + && apt-get install -y --no-install-recommends git python3 make g++ \ + && rm -rf /var/lib/apt/lists/* +RUN git clone https://github.com/fspecii/ace-step-ui.git /src \ + && cd /src \ + && git checkout --detach "$ACE_STEP_UI_COMMIT" +COPY patch-source.mjs /tmp/patch-source.mjs +RUN node /tmp/patch-source.mjs /src +RUN cd /src \ + && npm ci \ + && npm run build +RUN cd /src/server \ + && npm ci \ + && npm run build \ + && npm prune --omit=dev + +FROM node:22-bookworm-slim AS runtime + +RUN apt-get update \ + && apt-get install -y --no-install-recommends nginx curl ca-certificates ffmpeg \ + && rm -rf /var/lib/apt/lists/* +COPY --from=build /src/dist /usr/share/nginx/html +COPY --from=build /src/server/dist /app/server/dist +COPY --from=build /src/server/node_modules /app/server/node_modules +COPY --from=build /src/server/package.json /app/server/package.json +COPY --from=build /src/server/public /app/server/public +COPY --from=build /src/server/audio-editor /app/server/audio-editor +COPY nginx.conf /etc/nginx/nginx.conf +COPY entrypoint.sh /usr/local/bin/ace-step-ui-entrypoint +RUN chmod 0755 /usr/local/bin/ace-step-ui-entrypoint \ + && mkdir -p /data/audio /data/datasets/uploads + +EXPOSE 3000 3001 +ENTRYPOINT ["/usr/local/bin/ace-step-ui-entrypoint"] diff --git a/experiments/acestep15-xl-sft/ace-step-ui/entrypoint.sh b/experiments/acestep15-xl-sft/ace-step-ui/entrypoint.sh new file mode 100644 index 0000000..b2dae64 --- /dev/null +++ b/experiments/acestep15-xl-sft/ace-step-ui/entrypoint.sh @@ -0,0 +1,18 @@ +#!/bin/sh +set -eu + +node /app/server/dist/index.js & +server_pid=$! + +trap 'kill "$server_pid" 2>/dev/null || true' INT TERM EXIT +nginx -g 'daemon off;' & +nginx_pid=$! + +while kill -0 "$server_pid" 2>/dev/null && kill -0 "$nginx_pid" 2>/dev/null; do + sleep 1 +done + +kill "$server_pid" "$nginx_pid" 2>/dev/null || true +wait "$server_pid" 2>/dev/null || true +wait "$nginx_pid" 2>/dev/null || true +exit 1 diff --git a/experiments/acestep15-xl-sft/ace-step-ui/nginx.conf b/experiments/acestep15-xl-sft/ace-step-ui/nginx.conf new file mode 100644 index 0000000..1dc9237 --- /dev/null +++ b/experiments/acestep15-xl-sft/ace-step-ui/nginx.conf @@ -0,0 +1,35 @@ +worker_processes auto; +pid /tmp/nginx.pid; + +events { + worker_connections 1024; +} + +http { + include /etc/nginx/mime.types; + default_type application/octet-stream; + sendfile on; + client_max_body_size 512m; + + server { + listen 3000; + server_name _; + root /usr/share/nginx/html; + index index.html; + + location ~ ^/(api|audio|editor|blog|demucs-web)(/|$) { + proxy_pass http://127.0.0.1:3001; + proxy_http_version 1.1; + proxy_set_header Host $host; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_buffering off; + proxy_read_timeout 1800s; + proxy_send_timeout 1800s; + } + + location / { + try_files $uri $uri/ /index.html; + } + } +} diff --git a/experiments/acestep15-xl-sft/ace-step-ui/patch-source.mjs b/experiments/acestep15-xl-sft/ace-step-ui/patch-source.mjs new file mode 100644 index 0000000..bf27443 --- /dev/null +++ b/experiments/acestep15-xl-sft/ace-step-ui/patch-source.mjs @@ -0,0 +1,187 @@ +import fs from 'node:fs'; +import path from 'node:path'; + +const root = process.argv[2]; +if (!root) throw new Error('source root argument is required'); + +function patch(relativePath, transform) { + const filename = path.join(root, relativePath); + const before = fs.readFileSync(filename, 'utf8'); + const after = transform(before); + if (after === before) throw new Error(`patch made no change: ${relativePath}`); + fs.writeFileSync(filename, after); +} + +function replaceOnce(text, before, after, label) { + const first = text.indexOf(before); + if (first < 0) throw new Error(`patch anchor missing: ${label}`); + if (text.indexOf(before, first + 1) >= 0) throw new Error(`patch anchor repeated: ${label}`); + return text.slice(0, first) + after + text.slice(first + before.length); +} + +patch('server/src/services/acestep.ts', (text) => { + text = replaceOnce( + text, + "const AUDIO_DIR = path.join(__dirname, '../../public/audio');", + 'const AUDIO_DIR = config.storage.audioDir;', + 'persistent generated audio', + ); + + const oldArgs = ` params.instruction || 'Fill the audio semantic mask with the style described in the text prompt.', // 17: Instruction + params.audioCoverStrength ?? 1.0, // 18: Audio Cover Strength + 0.0, // 19: Cover Noise Strength (ACE-Step v1.5 new param, default 0.0) + (params.taskType === 'audio2audio' ? 'cover' : params.taskType) || 'text2music', // 20: Task Type + params.useAdg ?? false, // 21: Use ADG + params.cfgIntervalStart ?? 0.0, // 22: CFG Interval Start + params.cfgIntervalEnd ?? 1.0, // 23: CFG Interval End + params.shift ?? 3.0, // 24: Shift + params.inferMethod || 'ode', // 25: Inference Method + params.customTimesteps || '', // 26: Custom Timesteps + params.audioFormat || 'mp3', // 27: Audio Format + params.lmTemperature ?? 0.85, // 28: LM Temperature + isThinking, // 29: Think + params.lmCfgScale ?? 2.0, // 30: LM CFG Scale + params.lmTopK ?? 0, // 31: LM Top-K + params.lmTopP ?? 0.9, // 32: LM Top-P + params.lmNegativePrompt || 'NO USER INPUT', // 33: LM Negative Prompt + useCot ? (params.useCotMetas ?? true) : false, // 34: CoT Metas + useCot ? (params.useCotCaption ?? true) : false, // 35: CaptionRewrite + useCot ? (params.useCotLanguage ?? true) : false, // 36: CoT Language + params.isFormatCaption ?? false, // 37: Is Format Caption State + params.constrainedDecodingDebug ?? false, // 38: Constrained Decoding Debug + params.allowLmBatch ?? true, // 39: ParallelThinking + params.getScores ?? false, // 40: Auto Score + params.getLrc ?? false, // 41: Auto LRC (timestamped lyrics) + params.scoreScale ?? 0.5, // 42: Quality Score Sensitivity (0.01-1.0) + params.lmBatchChunkSize ?? 8, // 43: LM Batch Chunk Size + params.trackName || null, // 44: Track Name + params.completeTrackClasses || [], // 45: Track Names + true, // 46: Enable Normalization (ACE-Step v1.5, default true) + -1.0, // 47: Normalization DB (ACE-Step v1.5, default -1.0) + 0.0, // 48: Latent Shift (ACE-Step v1.5, default 0.0) + 1.0, // 49: Latent Rescale (ACE-Step v1.5, default 1.0) + params.autogen ?? false, // 50: AutoGen`; + + const newArgs = ` params.instruction || 'Fill the audio semantic mask based on the given conditions:', // 17: Instruction + params.audioCoverStrength ?? 1.0, // 18: LM Codes Strength + 0.0, // 19: Cover Strength + false, // 20: no_fsq + params.useAdg ?? false, // 21: Use ADG + params.cfgIntervalStart ?? 0.0, // 22: CFG Interval Start + params.cfgIntervalEnd ?? 1.0, // 23: CFG Interval End + params.shift ?? 1.0, // 24: Shift + params.inferMethod || 'ode', // 25: Inference Method + 'euler', // 26: Sampler Mode + 0.0, // 27: Velocity Norm Threshold + 0.0, // 28: Velocity EMA Factor + false, // 29: Enable DCW + 'double', // 30: DCW Mode + 0.02, // 31: DCW Scaler + 0.06, // 32: DCW High Scaler + 'haar', // 33: DCW Wavelet + params.customTimesteps || '', // 34: Custom Timesteps + params.audioFormat || 'flac', // 35: Audio Format + '320k', // 36: MP3 Bitrate + 48000, // 37: MP3 Sample Rate + params.lmTemperature ?? 0.85, // 38: LM Temperature + isThinking, // 39: Think + params.lmCfgScale ?? 2.0, // 40: LM CFG Scale + params.lmTopK ?? 0, // 41: LM Top-K + params.lmTopP ?? 0.9, // 42: LM Top-P + params.lmNegativePrompt || 'NO USER INPUT', // 43: LM Negative Prompt + useCot ? (params.useCotMetas ?? true) : false, // 44: CoT Metas + useCot ? (params.useCotCaption ?? true) : false, // 45: CaptionRewrite + useCot ? (params.useCotLanguage ?? true) : false, // 46: CoT Language + params.constrainedDecodingDebug ?? false, // 47: Constrained Decoding Debug + params.allowLmBatch ?? true, // 48: ParallelThinking + params.getScores ?? false, // 49: Auto Score + params.getLrc ?? false, // 50: Auto LRC + params.scoreScale ?? 0.5, // 51: Quality Score Sensitivity + params.lmBatchChunkSize ?? 8, // 52: LM Batch Chunk Size + params.trackName || null, // 53: Track Name + params.completeTrackClasses || [], // 54: Track Names + true, // 55: Enable Normalization + -1.0, // 56: Target Peak (dB) + 0.0, // 57: Fade In + 0.0, // 58: Fade Out + 0.0, // 59: Latent Shift + 1.0, // 60: Latent Rescale + 'balanced', // 61: Repaint Mode + 0.5, // 62: Repaint Strength + 0.0, // 63: variance + '', // 64: repaint seed + false, // 65: Edit + null, // 66: source caption + null, // 67: source lyrics + 0.0, // 68: n_min + 1.0, // 69: n_max + 1, // 70: n_avg + params.autogen ?? false, // 71: AutoGen`; + + return replaceOnce(text, oldArgs, newArgs, 'Athena Gradio argument schema'); +}); + +patch('server/src/services/storage/local.ts', (text) => { + text = replaceOnce( + text, + "import type { StorageProvider } from './index.js';", + "import type { StorageProvider } from './index.js';\nimport { config } from '../../config/index.js';", + 'storage config import', + ); + return replaceOnce( + text, + "const AUDIO_DIR = path.join(__dirname, '../../../public/audio');", + 'const AUDIO_DIR = config.storage.audioDir;', + 'persistent uploaded audio', + ); +}); + +patch('server/src/index.ts', (text) => replaceOnce( + text, + "app.use('/audio', express.static(path.join(__dirname, '../public/audio')));", + "app.use('/audio', express.static(config.storage.audioDir));", + 'persistent audio static route', +)); + +patch('server/src/routes/referenceTrack.ts', (text) => { + text = replaceOnce( + text, + "import { spawn } from 'child_process';", + "import { spawn } from 'child_process';\nimport { config } from '../config/index.js';", + 'reference audio config import', + ); + return replaceOnce( + text, + "const AUDIO_DIR = path.join(__dirname, '../../public/audio');", + 'const AUDIO_DIR = config.storage.audioDir;', + 'persistent reference audio', + ); +}); + +patch('server/src/routes/generate.ts', (text) => { + text = replaceOnce( + text, + " const ALL_DIT_MODELS = [\n 'acestep-v15-turbo',", + " const ALL_DIT_MODELS = [\n 'acestep-v15-xl-sft', // Athena production model\n 'acestep-v15-turbo',", + 'XL-SFT model list', + ); + + const start = text.indexOf("router.get('/limits'"); + const end = text.indexOf("router.get('/debug/", start); + if (start < 0 || end < 0) throw new Error('limits route anchors missing'); + const limits = `router.get('/limits', async (_req, res: Response) => { + // The UI container intentionally has no CUDA or ACE-Step Python runtime. + // These are the limits reported by Athena's dedicated RTX 5080 worker. + res.json({ + tier: process.env.ACESTEP_TIER || 'tier5', + gpu_memory_gb: Number(process.env.ACESTEP_GPU_MEMORY_GB || 15.5), + max_duration_with_lm: Number(process.env.ACESTEP_MAX_DURATION_WITH_LM || 480), + max_duration_without_lm: Number(process.env.ACESTEP_MAX_DURATION_WITHOUT_LM || 600), + max_batch_size_with_lm: Number(process.env.ACESTEP_MAX_BATCH_WITH_LM || 4), + max_batch_size_without_lm: Number(process.env.ACESTEP_MAX_BATCH_WITHOUT_LM || 4), + }); +}); + +`; + return text.slice(0, start) + limits + text.slice(end); +}); diff --git a/experiments/acestep15-xl-sft/compose.yaml b/experiments/acestep15-xl-sft/compose.yaml index c11ec1e..5b26c70 100644 --- a/experiments/acestep15-xl-sft/compose.yaml +++ b/experiments/acestep15-xl-sft/compose.yaml @@ -15,7 +15,8 @@ services: ACESTEP_DEVICE: cuda # The image entrypoint forwards only ACESTEP_EXTRA_ARGS to the UI CLI. # One result per run avoids the batch=2 VRAM/time penalty. - ACESTEP_EXTRA_ARGS: "--batch_size 1" + # Named Gradio endpoints are consumed by the separate ace-step-ui service. + ACESTEP_EXTRA_ARGS: "--batch_size 1 --enable-api" TOKENIZERS_PARALLELISM: "false" NVIDIA_VISIBLE_DEVICES: ${ACESTEP_GPU_UUID:?set ACESTEP_GPU_UUID to the RTX 5080 UUID} deploy: @@ -26,7 +27,8 @@ services: device_ids: ["${ACESTEP_GPU_UUID:?set ACESTEP_GPU_UUID to the RTX 5080 UUID}"] capabilities: [gpu] ports: - - "127.0.0.1:${ACESTEP_UI_PORT:-7861}:7860" + # Raw Gradio remains available for diagnostics; users open music-ui below. + - "127.0.0.1:${ACESTEP_GRADIO_PORT:-7862}:7860" volumes: - ${ACESTEP_CHECKPOINTS_DIR:-/data/models/acestep/checkpoints}:/app/checkpoints - ${ACESTEP_HF_CACHE_DIR:-/data/models/acestep/hf-cache}:/root/.cache/huggingface @@ -45,3 +47,45 @@ services: timeout: 10s start_period: 300s retries: 3 + networks: + - music + - frontend + + music-ui: + build: + context: ./ace-step-ui + args: + ACE_STEP_UI_COMMIT: a1fdf91829ec6f7b98844f80e323529cd155dbf2 + image: mike-ai/ace-step-ui:a1fdf918 + container_name: mike-ai-music-ui + environment: + NODE_ENV: production + PORT: "3001" + FRONTEND_URL: ${ACESTEP_UI_PUBLIC_URL:-http://192.168.1.212:7861} + ACESTEP_API_URL: http://music-worker:7860 + DATABASE_PATH: /data/acestep.db + AUDIO_DIR: /data/audio + DATASETS_DIR: /data/datasets + DATASETS_UPLOADS_DIR: /data/datasets/uploads + JWT_SECRET: ${ACESTEP_UI_JWT_SECRET:?set ACESTEP_UI_JWT_SECRET} + ports: + - "127.0.0.1:${ACESTEP_UI_PORT:-7861}:3000" + volumes: + - ${ACESTEP_UI_DATA_DIR:-/data/music/ace-step-ui}:/data + restart: unless-stopped + healthcheck: + test: ["CMD-SHELL", "curl -fsS http://127.0.0.1:3000/ >/dev/null && curl -fsS http://127.0.0.1:3001/health >/dev/null"] + interval: 30s + timeout: 10s + start_period: 30s + retries: 3 + networks: + - music + - frontend + +networks: + music: + name: mike-ai-music + frontend: + external: true + name: mike-ai_frontend diff --git a/platform/docker/wireguard-gateway/entrypoint.sh b/platform/docker/wireguard-gateway/entrypoint.sh index dfc5fec..f74c879 100644 --- a/platform/docker/wireguard-gateway/entrypoint.sh +++ b/platform/docker/wireguard-gateway/entrypoint.sh @@ -78,6 +78,8 @@ start_proxy 8081 router:8081 start_proxy 8085 tts-gateway:8085 start_proxy 8091 piper:8085 start_proxy 8099 llama-dashboard:8099 +start_proxy 7861 music-ui:3000 +start_proxy 7862 music-worker:7860 start_proxy 8202 mcp-athena-operator:8000 start_proxy 9443 portainer:9443 diff --git a/platform/llama-dashboard/app.py b/platform/llama-dashboard/app.py index 4ba42f5..2f7a162 100644 --- a/platform/llama-dashboard/app.py +++ b/platform/llama-dashboard/app.py @@ -20,7 +20,13 @@ HOST = os.getenv("DASHBOARD_HOST", "0.0.0.0") PORT = int(os.getenv("DASHBOARD_PORT", "8099")) ROUTER_URL = os.getenv("ROUTER_URL", "http://router:8081").rstrip("/") ROUTER_API_KEY = os.getenv("ROUTER_API_KEY", "") -MUSIC_UI_URL = os.getenv("MUSIC_UI_URL", "http://127.0.0.1:7861/") +MUSIC_COMMUNITY_UI_URL = os.getenv( + "MUSIC_COMMUNITY_UI_URL", + os.getenv("MUSIC_UI_URL", "http://192.168.1.212:7861/"), +) +MUSIC_ORIGINAL_UI_URL = os.getenv( + "MUSIC_ORIGINAL_UI_URL", "http://192.168.1.212:7862/" +) HOST_PROC = Path(os.getenv("HOST_PROC", "/host/proc")) HOST_DATA = os.getenv("HOST_DATA", "/host/data") HOST_MODELS = Path(os.getenv("HOST_MODELS", "/host/models")) @@ -624,11 +630,11 @@ HTML = r''' .span4 .metrics{grid-template-columns:repeat(2,1fr)} .history-controls{display:flex;flex-wrap:wrap;gap:7px;margin:10px 0 14px}.history-controls button{border:1px solid var(--line);background:#09111b;color:var(--muted);padding:6px 10px;border-radius:8px;cursor:pointer}.history-controls button.active{color:var(--cyan);border-color:var(--cyan)}.chart-legend{display:flex;flex-wrap:wrap;gap:8px;margin:2px 0 10px}.chart-legend button{border:1px solid var(--series);background:#09111b;color:var(--text);padding:6px 10px;border-radius:8px;cursor:pointer}.chart-legend button::before{content:'';display:inline-block;width:10px;height:3px;background:var(--series);margin:0 7px 3px 0}.chart-legend button.off{opacity:.4;text-decoration:line-through}.chart{width:100%;height:250px;display:block}.token-total{font-size:24px;font-weight:750;margin-top:5px}.history-note{color:var(--muted);font-size:11px;margin-top:8px} .usage-list{display:grid;gap:13px;margin-top:8px}.usage-head{display:flex;justify-content:space-between;gap:14px;align-items:baseline}.usage-head b{font-size:16px}.usage-head span{color:var(--muted)}.usage-meta{display:flex;justify-content:space-between;gap:12px;color:var(--muted);font-size:11px;margin-top:5px} -.mode-row{display:flex;align-items:center;justify-content:space-between;gap:18px;flex-wrap:wrap}.mode-buttons{display:flex;gap:9px;flex-wrap:wrap}.mode-buttons button,.mode-buttons a{border:1px solid var(--line);background:#09111b;color:var(--text);padding:9px 14px;border-radius:9px;cursor:pointer;text-decoration:none;font:inherit}.mode-buttons button.active{border-color:var(--cyan);color:var(--cyan);box-shadow:inset 0 0 0 1px #45d7ff33}.mode-buttons button:disabled{opacity:.45;cursor:wait}.mode-buttons a[hidden]{display:none}.mode-error{color:var(--red)} +.mode-row{display:flex;align-items:center;justify-content:space-between;gap:18px;flex-wrap:wrap}.mode-buttons,.mode-buttons span{display:flex;gap:9px;flex-wrap:wrap}.mode-buttons button,.mode-buttons a{border:1px solid var(--line);background:#09111b;color:var(--text);padding:9px 14px;border-radius:9px;cursor:pointer;text-decoration:none;font:inherit}.mode-buttons button.active{border-color:var(--cyan);color:var(--cyan);box-shadow:inset 0 0 0 1px #45d7ff33}.mode-buttons button:disabled{opacity:.45;cursor:wait}.mode-buttons span[hidden]{display:none}.mode-buttons a.stable{border-color:#66e3a466;color:var(--green)}.mode-buttons a.experimental{border-color:#ffc65c66;color:var(--amber)}.mode-error{color:var(--red)}