--- title: "Why My AI Songs Kept Losing Their Endings: Running YuE2 on a 12 GB GPU" description: "Twelve Mandarin songs came out exactly 240.000 seconds long with the outros missing. The cause wasn't the lyrics — it was a hard cap, a VRAM ceiling, and a planner that ignores BPM." pubDate: 2026-10-20 category: ai tags: ["yue2", "comfyui", "ai-music", "local-ai", "gpu", "vram", "mandarin"] ogImage: "/og/yue2-on-a-12gb-gpu-songs-that-lost-their-endings.jpg" banner: "/banners/yue2-on-a-12gb-gpu-songs-that-lost-their-endings.jpg" draft: false --- YuE2 is a 3B open-weights music model that sings Mandarin from a style prompt and a lyric sheet. I ran it locally on a 12 GB RTX 3060 through ComfyUI — twelve original songs in one serial queue — and the first batch came back with **every single file exactly 240.000 seconds long**. That number is not a coincidence, and it is not good news. ## Every track was exactly 240.000 seconds Twelve files, twelve identical durations to the millisecond: ``` 01_A1_..._糖画小仙.mp3 duration=240.000000 02_A2_..._溪边捉云记.mp3 duration=240.000000 03_A3_..._寒露拾桂子.mp3 duration=240.000000 ... ``` I had asked for `max_duration=240` because the style guide I was working from said a 12-section structure would land around three and a half minutes. It landed on 240 seconds exactly — which means the model never decided to stop. It ran out of room. ## A round number is a confession YuE2 plans a song with an ABC planner: it reads the lyric sheet, decides how many bars each section gets, and emits a symbolic sketch. The music generator then sings that sketch and **ends when the sketch ends**. A finished song therefore comes out *shorter* than the cap you set — it stops on its own. Here is the tell, and it costs one shell command: ```bash d=$(ffprobe -v error -show_entries format=duration -of csv=p=0 take.mp3) python -c "print(abs(float('$d') - 240.0) <= 0.005)" # True = hard cut ``` If the duration equals `max_duration` to within a few milliseconds, the render was truncated. The lyric sheet wanted more time than the cap allowed, and the last thing being sung at the cutoff point got sliced off mid-phrase. ## Confirming the outros were actually missing Duration alone tells you *that* it was cut, not *what* was lost. So I pulled the tail of each take and transcribed it with a local speech model (SenseVoice, CPU — it stays running while the GPU renders): ```bash ffmpeg -v error -y -i take.mp3 -ss 232 -t 20 -ar 16000 -ac 1 -c:a pcm_s16le t.wav curl -s -X POST http://127.0.0.1:20129/v1/audio/transcriptions \ -F "file=@t.wav" -F "language=zh" ``` The results were unambiguous: | Song | Sung up to | What was missing | |---|---|---| | 糖画小仙 | final chorus, last line | the entire `[Outro]` | | 溪边捉云记 | `[Bridge]` at 215 s | solo + final chorus + outro | | 寒露拾桂子 | `[Bridge]` at 215 s | final chorus + outro | | 纸鸢与旧木偶 | final chorus, line 5 | rest of chorus + outro | So: not a mixing artefact, not a fade-out. The songs were structurally incomplete. ## Three fixes that did nothing, and one that half-worked ### 1. Raising the BPM — no effect The obvious reasoning: faster tempo, more syllables per second, more lyrics fit in the same window. I tested this across the batch: the 117 BPM guofeng tracks and the 140 BPM DJ remixes had **effectively identical throughput**. YuE2's duration comes from the ABC planner's bar count, not from the BPM token in your style prompt. The BPM tag shapes the *feel*; it does not buy you room. Stop tuning it for duration. ### 2. Re-running with the same seed — silently does nothing ComfyUI was started with `--cache-classic`, so resubmitting a byte-identical prompt — same style, same lyrics, same seed — hits the node cache. The job "completes" in about ten seconds and hands you back the same truncated audio with a `_00002` filename. It looks like a fast, successful retry. It is a cache hit on a known-bad take. ### 3. Rolling new seeds — works about 3 times out of 4 The planner's bar allocation depends on the seed, so a different seed really can produce a shorter, complete song from the same lyrics. This rescued three of my four clipped tracks: | Song | Original | New seed | Result | |---|---|---|---| | 溪边捉云记 | clipped at 281 s | 20002 | 279.7 s, complete | | 外婆的绿搪瓷碗 | clipped at 300 s | 20007 | 251.9 s, complete | | 北溟有小鱼 | clipped at 300 s | 20005, 30005 | clipped again | ## The budget is characters per second — and it isn't constant I tried to turn this into arithmetic: count the Chinese characters, divide by a throughput figure, get the duration. The measurements refused to cooperate. | Song | Characters | Cap | Actual | Fits? | |---|---|---|---|---| | 糖画小仙 | 556 | 272 s | 258.7 s | ✅ | | 纸鸢与旧木偶 | 572 | 300 s | 279.9 s | ✅ | | 溪边捉云记 | 578 | 281 s | **281.0 s** | ❌ cut | | 小楼檐下听雨眠 | 580 | 300 s | 256.3 s | ✅ | | 画卷小锦鲤 | 634 | 300 s | 285.9 s | ✅ | | 三楼窗台的蒲公英 | 693 | 329 s | 247.4 s | ✅ | | 弄堂口的竹蜻蜓 | 692 | 300 s | 250.0 s | ✅ | Read the last two rows against the third. **693 characters finished in 247 seconds, while 578 characters overflowed 281 seconds.** A lyrics-to-duration formula simply does not exist here — the observed throughput ranges from about 2.0 to 2.8 Chinese characters per second, depending on how the planner chops the sections. So characters are useful for a *budget warning*, not for setting a timer. ## The limit that actually binds: VRAM Once you accept "give it enough room and let it close naturally", the cap wants to be large. On a 12 GB card it cannot be. Measured peak VRAM while rendering, sampled every five seconds with `nvidia-smi` during the run: | `max_duration` | Peak VRAM | |---|---| | 240 s | 10.3 GB | | 272 s | 11.65 GB | | 278 s | 11.07 GB | | 281 s | 12.06 GB | | 300 s | 12.03 GB | The card has 12.288 GB. **300 seconds is the practical ceiling** — past that you are one desktop browser tab away from an out-of-memory abort in the middle of a six-minute render. ## The fix Three changes, all cheap: 1. **Set the cap to 300 s and let the planner close the song itself.** Do not chase a target length; give it headroom and it ends where the music ends. 2. **Archive clipped takes instead of mixing them in.** A take whose duration equals the cap goes to `未自然结尾\` ("did not end naturally"). The output folder then contains only complete songs, and re-renders never destroy a usable take. 3. **Cap the seed roulette at three attempts.** Different seeds help, but a song whose lyrics genuinely need more than the VRAM ceiling allows will fail forever. Three strikes and you trim the lyric sheet — don't burn an hour of GPU pretending the next seed will be the one. ## The song that never fit One of the twelve never made it. **北溟有小鱼 — 593 characters — failed five times:** | Attempt | Parameters | Result | |---|---|---| | 1 | seed 10005, cap 287 s | hit 287 s | | 2 | seed 10005, cap 300 s | hit 300 s | | 3 | seed 20005, cap 300 s | hit 300 s | | 4 | seed 30005, cap 300 s | hit 300 s | | 5 | seed 40005, both instrumental sections removed | hit 300 s | I even deleted the `[Interlude]` and `[Instrumental Solo]` markers — 12 sections down to 10, not one sung word changed — and it still overflowed. Its planner wants more time than a 12 GB card can give. The honest fix is to cut about 40 characters of lyric and re-render, and I would rather tell you that than ship a twelfth track with a bitten-off ending. ## A trap I hit before any of this Worth its own warning: **YuE2 will sing the Chinese production notes in your lyric sheet.** If you paste lyrics that look like this — which is how most lyric sheets are written — ``` [Intro] (重鼓点起,急促琵琶轮指,唢呐破空,合成器铺底) 嘿!哈!山海震! ``` …then that parenthetical line becomes a lyric. The model does not know it is a stage direction. Strip them before submitting: ```python clean = "\n".join(l for l in raw.split("\n") if not re.match(r"^\s*[((].*[))]\s*$", l)) ``` Arrangement and dynamics belong in the style prompt. The lyric box gets section tags and sung words, nothing else. ## What I'd do differently - **Pre-flight the lyric sheet, not the render.** Count characters first. Over roughly 600 characters on a 12 GB card, expect trouble; over 550, expect to trim something. - **Never set `max_duration` to a target length.** Set it generously above the estimate and let the planner finish. The cap is a safety rail, not a dial. - **Treat "duration == cap" as a hard failure in the pipeline**, not something you notice three days later when a chorus sounds wrong. - **Re-run with a new seed before touching the lyrics** — it is free and works most of the time. Then stop at three. ## Eleven tracks These are the ones that closed naturally, sung by a child voice, in Mandarin, on a 12 GB card that cost less than a year of cloud music credits. Each player below has synced lyrics — the timing was recovered by transcription and aligned against the written lyrics, since the model emits no timestamps. ### 1. 糖画小仙 — Sugar-Painting Fairy *Guofeng electronic* · 556 characters · 259 s
### 2. 溪边捉云记 — Catching Clouds by the Stream *Guofeng electronic* · 578 characters · 280 s
### 3. 寒露拾桂子 — Picking Osmanthus at Cold Dew *Guofeng electronic* · 578 characters · 290 s
### 4. 纸鸢与旧木偶 — The Kite and the Old Puppet *Guofeng ballad* · 572 characters · 280 s
### 5. 小楼檐下听雨眠 — Listening to Rain Under the Eaves *Guofeng ballad* · 580 characters · 256 s
### 6. 外婆的绿搪瓷碗 — Grandma's Green Enamel Bowl *Urban folk* · 679 characters · 252 s
### 7. 弄堂口的竹蜻蜓 — The Bamboo Dragonfly at the Alley Mouth *Urban folk* · 692 characters · 250 s
### 8. 三楼窗台的蒲公英 — Dandelion on the Third-Floor Windowsill *Urban folk* · 693 characters · 247 s
### 9. 小哪吒借红绫 — Little Nezha Borrows the Red Sash *Guofeng DJ remix* · 612 characters · 271 s
### 10. 画卷小锦鲤 — The Koi That Left the Painting *Guofeng DJ remix* · 634 characters · 286 s
### 11. 山海小当家 — Little Boss of the Classic of Mountains and Seas *Guofeng DJ remix* · 647 characters · 237 s