YuE2 Outside ComfyUI: What the Node Graph Was Hiding
Rebuilding a local YuE2 music pipeline on the project's official Python runtime exposed truncation flags, a CFG knob and per-stage receipts that ComfyUI's nodes never surface — and disproved a tempo finding published three days earlier.
TL;DR — Moving YuE2 off ComfyUI and onto the project's own
yue2-inferruntime surfaced a truncation flag, a CFG knob and a per-stage receipt that the ComfyUI nodes never expose — and the first thing they proved was that a conclusion I had written down three days earlier was wrong.
I have been generating K-pop dance tracks locally with YuE2, the open lyrics-to-song model from the Multimodal Art Projection team, on a single RTX 5080 with 16 GB of VRAM. For a week I drove it through ComfyUI's built-in nodes, because that is the path of least resistance. Then I rebuilt the same work on the official Python runtime. As of 15 September 2026, here is what changed — and the honest part is that most of it was information I had been missing, not quality I had been losing.
The node graph and the official request are not the same surface
This is the part worth internalising if you are deciding where to run a model like this. ComfyUI's YuE2GenerateABC and YuE2GenerateMusic nodes expose eleven sampling inputs between them. The official request schema has seven fields — id, style, lyrics, cot, seed, abc, cfg_scale. The overlap is smaller than it looks, and the gaps run in one direction.
| Capability | ComfyUI YuE2 nodes | Official yue2-infer 0.1.6 |
|---|---|---|
| Truncation reporting | none | truncated.abc and truncated.semantic in result.json |
Semantic CFG (cfg_scale) |
not exposed at all | request field; defaults 1.0 for full/melody, 1.01 for off |
| Conditioning-mode contract | a mode string, no rules |
cot = full / melody / off, each with a documented ABC-input contract |
| Synthesis steps | whatever you set on KSampler (the template ships 20) | the release preset is fixed: 32 midpoint steps, context 24576 |
| Per-stage timing | none | seconds and tokens/s for ABC, semantic, NAR and VAE separately |
| Reproducibility receipt | none | weight SHA-256, config hash, request, seed, decoder identity, artifact hashes |
| Re-decode a finished song | not offered | latent.npy is saved; swap the decoder without regenerating the music |
| Memory levers | none | offload_ar, memory_budget_gib, vae_core_frames, query_chunk_size |
| Plan round-trip | manual string wiring between nodes | plan.save() / SymbolicPlan.load() with integrity validation |
None of these are quality features in themselves. They are observability features, and the difference is that without them you cannot tell a bad result from a misread one.
Two patches to run a 24 GB pipeline on 16 GB
The documentation is explicit that the supported baseline is "a BF16-capable NVIDIA GPU with 24GB memory and one active request per pipeline." On 16 GB the stock install died twice.
First, RuntimeError: USE_FLASH_ATTENTION was not enabled for build. Windows PyTorch wheels ship without the FlashAttention kernel, but YuE2's backend auto-detection only checks that the aten._flash_attention_forward operator exists and that its schema mentions seqused_k. On Windows the operator is registered, so detection passes and execution fails. The backend is a keyword-only argument that the pipeline does not forward, so the fix is to default GraphAR to the cuDNN backend. CUDA graphs still work.
Second, an out-of-memory error in the non-autoregressive synthesis stage on a single 3.77 GiB allocation. On CUDA the NAR attention helper sets its block size to the full query length, so it materialises the entire sequence at once. Enabling offload_ar (which moves the ~6 GB autoregressive model to the CPU during synthesis) and setting query_chunk_size to 2048 both fit inside the documented API surface and are mathematically identical splits.
After that, the project's own example generated 213.8 seconds of audio in 238.7 seconds: 20.5 s for the ABC score, 48.7 s for semantic tokens, 161.4 s for NAR synthesis and 4.0 s for the VAE. NAR is 68% of the wall clock, which is why the ComfyUI route felt four times faster — its template runs 20 sampler steps where the release preset runs 32.
The flag that ended a week of guessing
In ComfyUI I would request 180 seconds and get 98. I had no way to tell whether the song was being cut off or simply ending. The official runtime writes truncated.abc and truncated.semantic into result.json.
Across 29 runs, both flags were false every single time. The songs were not truncated. They were finished. Length is set by how much lyric there is to sing — the project's own 213-second example simply has far more words. That single boolean retired a week of speculation and a script I had written to work around a problem that did not exist.
What I had wrong: the model does follow BPM
Three days earlier I had written down, as a finding, that YuE2 ignores tempo instructions. Checking all 28 runs against the tempo written into each generated score:
| Requested in style | Written into the score |
|---|---|
| 112 BPM | 118 |
| 118 BPM | 122 |
| 124 BPM | 128 |
| 126 BPM | 128–132 |
| 132 BPM | 132 |
| 136 BPM | 134 |
| 140 BPM | 145 |
| 144 BPM | 140 |
Mean absolute error 3.5 BPM, monotonic across the range. The model follows tempo. My earlier evidence was a prompt containing the range "128–145", which produced a score marked 177 — and I generalised from one malformed input. Give it one number and it tracks.
Language is decided by the lyrics, not by the style tag
Community guides recommend matching the language named in the style prompt to the language of the lyrics. I tested that directly: hold the lyrics fixed, change only the language token, and measure both the detected language and how much of the actual lyric was sung, using automatic transcription.
| Lyrics | Language token in style | Language detected | Lyric fidelity |
|---|---|---|---|
| Korean | Korean |
Korean, 97% | 74% |
| Korean | English |
Korean, 97% | 57% |
| Korean | none | Korean, 97% | 71% |
| English | English |
English, 69% | 28% |
| English | Korean |
English, 71% | 3% |
| Korean + English mixed | both | Korean, 89% | 19% |
The language token does not choose the language — the lyrics do. Korean lyrics tagged English still came out Korean at 97% confidence. What the mismatch costs is fidelity: the English track dropped from 28% to 3% and degenerated into "Ah, ah, ah." Omitting the token entirely is nearly free, which matches the project's own showcase example, whose style prompt names no language at all while its lyrics are entirely in Chinese.
Notes per syllable has a ceiling, not just a floor
I already knew that too few notes per syllable makes the model rap the lyrics instead of singing them. The opposite wall is real too:
- Korean: 185 syllables over 208 notes → 1.12 → sung faithfully (74%)
- English: 119 syllables over 240 notes → 2.02 → half the notes filled with "oh oh oh" (28%)
When the plan has twice as many notes as the words can occupy, the model invents vocalisations. The usable band is roughly 1.0 to 1.3.
One generation is a lottery
The documentation is unusually blunt about this. On the generation page: "One pipeline call produces one candidate. The benchmark's candidate selection is a separate evaluation step." On the benchmark page: "Standard YuE2 selects the lower-PER candidate from two generations."
That matters for reading the published scores. YuE2 at best-of-8 posts a SongBench average of 6.9632, ahead of Suno v5 at 6.8721 and Suno v6 at 6.5562; the unselected setting scores 6.7316. I ran eight generations differing only by seed. Lyric fidelity came out 0, 31, 44, 45, 50, 57, 66 and 70%. The worst one had perfectly normal audio — 136 seconds, normal dynamics and spectrum — and produced an empty transcript. Audible, but unintelligible.
If you generate once, you are sampling from that distribution blind.
All four clips were generated locally by YuE2 for this write-up, at 128 kbps. Nothing was edited, mixed or re-taken.
FAQ
Can YuE2 run on a 16 GB GPU?
Yes, with two changes. The documented baseline is a 24 GB NVIDIA card, and a stock install fails twice on 16 GB: once because the Windows PyTorch wheel has no FlashAttention kernel, and once because the non-autoregressive synthesis stage feeds its whole query sequence into a single attention call. Forcing the cuDNN attention backend, enabling offload_ar, and chunking the query fixed both without changing the output. A 213.8-second song then took 238.7 seconds end to end.
Does the style prompt control BPM in YuE2?
Yes, if you write a single number. Across 28 runs the tempo written into the generated score tracked the requested BPM with a mean absolute error of 3.5 BPM, monotonically from 112 to 144. A tempo range in the prompt ("128–145") is what breaks it — that produced a score marked 177 BPM.
Why does YuE2 sing "oh oh oh" instead of my lyrics?
Usually because there are more notes in the plan than syllables in the lyrics. In a matched pair, Korean lyrics at 185 syllables over 208 notes (1.12 notes per syllable) were sung faithfully, while English lyrics at 119 syllables over 240 notes (2.02) were half-filled with vocalisations. Aim for roughly 1.0–1.3 notes per syllable.
Should I mix languages in one set of lyrics?
No. In this test the bilingual lyric set scored the lowest fidelity of any condition, 19%, and the transcript degraded at each point where the language switched mid-line.
Is one generation enough?
No. Eight runs that differed only by seed scored 0, 31, 44, 45, 50, 57, 66 and 70% on lyric fidelity. The published benchmark numbers use candidate selection, so a single unselected call is not the same setting.
Sources: YuE2 repository (multimodal-art-projection/YuE), YuE2 generation docs, YuE2 benchmark results, generation and covers reference, m-a-p/YuE2-3B on Hugging Face, WildSongBench dataset.
Image: Johann Ernst Bach, Magnificat autograph manuscript, Public domain, via Wikimedia Commons.
← Back to all posts