"""MLTNT — Militant Roots Reggae LoRAs for YuE2-3B. The four `becausereasons/yue2-mltnt-militant-reggae` LoRAs were trained with the ComfyUI FS_Audio Suite against Comfy-Org's repack of YuE2-3B, so they ship in Comfy's fused-key layout (`text_encoders.*` planner / `diffusion_model.*` decoder). This Space runs the *official* m-a-p `yue2_infer` pipeline instead of ComfyUI, and merges the LoRA into the native checkpoint by splitting Comfy's fused `qkv_proj` / `gate_up_proj` tensors back into the native `q/k/v_proj` and `gate/up_proj` weights (verified bit-exact against both checkpoints). The FS_Audio sampler's "Weirdness (cfg)" knob is reproduced as classifier-free guidance on the flow-matching decoder against a zeroed AR context, which is exactly what the Comfy graph does. """ from __future__ import annotations import json import os import subprocess import tempfile import threading import time from dataclasses import replace from pathlib import Path os.environ.setdefault("PYTORCH_CUDA_ALLOC_CONF", "expandable_segments:True") import spaces # noqa: E402 — must precede torch / CUDA-touching imports import gradio as gr # noqa: E402 import numpy as np # noqa: E402 import torch # noqa: E402 from huggingface_hub import hf_hub_download # noqa: E402 from safetensors.torch import load_file # noqa: E402 from yue2 import YuE2Pipeline # noqa: E402 from yue2.modeling_vae import YuE2VAE # noqa: E402 from yue2.nar import CachedNAR, song_chunks # noqa: E402 from yue2.protocol import Sampling # noqa: E402 BASE_ID = "m-a-p/YuE2-3B" VAE_ID = "m-a-p/YuE2-Vae" LORA_ID = "becausereasons/yue2-mltnt-militant-reggae" VARIANTS = { "MLTNT Soundclash — new-trainer generation, most expressive voice: hip-hop, dancehall, lovers rock": "mltnt_soundclash", "MLTNT Chanter — new-trainer generation, settled signature voice": "mltnt_chanter", "MLTNT Steppers — flagship, hard steppers + anthemic chorus": "mltnt_steppers", "MLTNT Frontline — newest, most distinctive voice, best for dense lyrics": "mltnt_frontline", "MLTNT Fusion — reggae hip-hop, boom-bap over one-drop": "mltnt_fusion", "MLTNT Roots — the purist, straightest roots timbre": "mltnt_roots", } DEFAULT_VARIANT = "MLTNT Steppers — flagship, hard steppers + anthemic chorus" # Files trained with AI Toolkit: lora_A / lora_B key names, no projection diffs, and a planner that # over-commits at full strength on fast styles, so they get their own suggested defaults. NEW_TRAINER_STEMS = {"mltnt_soundclash", "mltnt_chanter"} FPS = 25 # semantic codec frames per second, as in the FS_Audio sampler # ---------------------------------------------------------------------------- # Comfy fused-key -> native m-a-p key map # ---------------------------------------------------------------------------- N_LAYERS = 28 H = 2048 # hidden_size (16 heads x 128) KV = 1024 # 8 kv heads x 128 INTER = 6144 # intermediate_size MERGE_PLAN: list[tuple[str, str, list[tuple[str, int, int]]]] = [] for _i in range(N_LAYERS): for _root, _kind, _attn, _mlp in ( ("text_encoders", "clip", "self_attn", "mlp"), ("diffusion_model", "model", "nar_self_attn", "nar_mlp"), ): _c = f"{_root}.model.layers.{_i}" _n = f"model.layers.{_i}" MERGE_PLAN.append((f"{_c}.self_attn.qkv_proj", _kind, [ (f"{_n}.{_attn}.q_proj.weight", 0, H), (f"{_n}.{_attn}.k_proj.weight", H, H + KV), (f"{_n}.{_attn}.v_proj.weight", H + KV, H + 2 * KV), ])) MERGE_PLAN.append((f"{_c}.self_attn.o_proj", _kind, [ (f"{_n}.{_attn}.o_proj.weight", 0, H), ])) MERGE_PLAN.append((f"{_c}.mlp.gate_up_proj", _kind, [ (f"{_n}.{_mlp}.gate_proj.weight", 0, INTER), (f"{_n}.{_mlp}.up_proj.weight", INTER, 2 * INTER), ])) MERGE_PLAN.append((f"{_c}.mlp.down_proj", _kind, [ (f"{_n}.{_mlp}.down_proj.weight", 0, H), ])) # Full-rank projection diffs the trainer also writes (decoder strength). DIFF_PLAN = [ ("diffusion_model.llm2vae.diff", "llm2vae.weight", "model"), ("diffusion_model.llm2vae.diff_b", "llm2vae.bias", "model"), ("diffusion_model.vae2llm.diff", "vae2llm.weight", "model"), ("diffusion_model.vae2llm.diff_b", "vae2llm.bias", "model"), ] PATCHED_NAMES = sorted( {name for _, _, targets in MERGE_PLAN for name, _, _ in targets} | {name for _, name, _ in DIFF_PLAN} ) # ---------------------------------------------------------------------------- # Load everything at module scope so ZeroGPU can pack the weights. # ---------------------------------------------------------------------------- print("[mltnt] resolving YuE2 pipeline ...", flush=True) # device="cpu" keeps every torch.cuda.* call out of __init__; we attach the real # string device ("cuda", never "cuda:0") ourselves right after. pipe = YuE2Pipeline.from_pretrained( BASE_ID, vae=VAE_ID, device="cpu", backend="torch", verify_hashes=False, progress=False, ) pipe.device = torch.device("cuda") pipe.vae_core_frames = 1024 print("[mltnt] loading base model ...", flush=True) model = pipe._load_model() # -> .to(torch.device("cuda")) print("[mltnt] loading VAE decoder ...", flush=True) pipe._vae = YuE2VAE.from_pretrained( pipe.vae_dir, decoder_only=True, device="cpu", local_files_only=True ).to("cuda") vae = pipe._vae print("[mltnt] downloading LoRAs ...", flush=True) # .clone() so the tensors are anonymous RAM: ZeroGPU deletes the Hub cache blobs # after packing, which would pull the mmap out from under a zero-copy load. def _canonical_key(key: str) -> str: # AI Toolkit stores the same two matrices as lora_A (= down) and lora_B (= up); rank == alpha, so scale 1. return key.replace(".lora_A.weight", ".lora_down.weight").replace(".lora_B.weight", ".lora_up.weight") LORAS = { stem: {_canonical_key(k): v.clone() for k, v in load_file(hf_hub_download(LORA_ID, f"{stem}.safetensors")).items()} for stem in sorted(set(VARIANTS.values())) } # Fail loudly if the LoRA key layout ever drifts away from MERGE_PLAN/DIFF_PLAN: # an unmatched key would otherwise be skipped silently and the demo would quietly # serve the un-LoRA'd base model. _PLAN_KEYS = {f"{p}.lora_up.weight" for p, _, _ in MERGE_PLAN} \ | {f"{p}.lora_down.weight" for p, _, _ in MERGE_PLAN} \ | {k for k, _, _ in DIFF_PLAN} for _stem, _sd in LORAS.items(): if not _sd: continue _unmatched = sorted(set(_sd) - _PLAN_KEYS) _missing = sorted(_PLAN_KEYS - set(_sd)) if _stem in NEW_TRAINER_STEMS: # these files carry no vae2llm / llm2vae projection diffs; _apply_lora already skips absent ones _missing = [k for k in _missing if k not in {d for d, _, _ in DIFF_PLAN}] print(f"[mltnt] {_stem}: {len(_sd)} keys, " f"{len(_PLAN_KEYS) - len(_missing)}/{len(_PLAN_KEYS)} covered", flush=True) if _unmatched or _missing: raise RuntimeError( f"LoRA key layout mismatch in {_stem}: " f"unmatched={_unmatched[:5]} missing={_missing[:5]}" ) print("[mltnt] ready.", flush=True) _LOCK = threading.Lock() # Per-worker record of what the GPU weights currently hold, plus a per-worker # pristine copy of every tensor the merge touches. A cold fork starts from the # unpatched weights, so the snapshot it takes is always the true base. _APPLIED: dict[str, object] = {"key": None} _BASE: dict[str, torch.Tensor] = {} @torch.no_grad() def _snapshot_base() -> None: if _BASE: return params = dict(model.named_parameters()) for name in PATCHED_NAMES: _BASE[name] = params[name].data.clone() @torch.no_grad() def _restore_base() -> None: params = dict(model.named_parameters()) for name, base in _BASE.items(): params[name].data.copy_(base) _APPLIED["key"] = None @torch.no_grad() def _apply_lora(sd: dict, strength_model: float, strength_clip: float) -> None: params = dict(model.named_parameters()) for prefix, kind, targets in MERGE_PLAN: strength = strength_model if kind == "model" else strength_clip if strength == 0.0: continue up = sd.get(f"{prefix}.lora_up.weight") down = sd.get(f"{prefix}.lora_down.weight") if up is None or down is None: continue # No alpha key in these files -> delta = strength * (up @ down). delta = torch.mm(up.to("cuda", torch.float32), down.to("cuda", torch.float32)) delta.mul_(strength) for name, lo, hi in targets: p = params[name] p.data.copy_((p.data.float() + delta[lo:hi]).to(p.dtype)) del delta for key, name, kind in DIFF_PLAN: strength = strength_model if kind == "model" else strength_clip diff = sd.get(key) if diff is None or strength == 0.0: continue p = params[name] p.data.copy_((p.data.float() + diff.to("cuda", torch.float32) * strength).to(p.dtype)) def _ensure_merged(stem: str, strength_model: float, strength_clip: float) -> float: key = (stem, round(float(strength_model), 4), round(float(strength_clip), 4)) if _APPLIED["key"] == key: return 0.0 start = time.perf_counter() _snapshot_base() _restore_base() # GPU->GPU, idempotent, so a half-finished merge can't stack _apply_lora(LORAS[stem], float(strength_model), float(strength_clip)) _APPLIED["key"] = key torch.cuda.synchronize() return time.perf_counter() - start # ---------------------------------------------------------------------------- # FS_Audio "Weirdness (cfg)" == CFG on the flow-matching decoder, with the # autoregressive KV context zeroed for the negative branch. # ---------------------------------------------------------------------------- class GuidedNAR(CachedNAR): def __init__(self, *args, cfg: float = 1.0, **kwargs): self.cfg = float(cfg) super().__init__(*args, **kwargs) @torch.inference_mode() def velocity(self, state, raw_t): conditional = super().velocity(state, raw_t) if self.cfg == 1.0: return conditional real = self.cache zero = torch.zeros_like(real[0][0]) self.cache = [(zero, zero)] * len(real) try: unconditional = super().velocity(state, raw_t) finally: self.cache = real return unconditional + self.cfg * (conditional - unconditional) @torch.inference_mode() def _synthesize(prefix, codec, seed, steps, context, cfg, on_progress=None): chunks = song_chunks(prefix, codec, seed, context) out = [] for index, chunk in enumerate(chunks): engine = GuidedNAR(model, chunk, cfg=cfg) try: report = None if on_progress is not None: def report(done, total, _i=index, _n=len(chunks)): on_progress(_i * total + done, total * _n) out.append(engine.solve(steps, None, on_progress=report)) finally: engine.close() del engine return torch.cat(out, dim=0) @torch.inference_mode() def _decode(latents, on_progress=None): z = torch.as_tensor(latents, dtype=torch.float32) if z.ndim == 2 and z.shape[1] == 64: z = z.T.unsqueeze(0) audio = vae.decode_tiled( z, core_frames=pipe.vae_core_frames, halo_frames=16, output_device="cpu", on_progress=on_progress, ) if not torch.isfinite(audio).all(): raise gr.Error("The decoder produced non-finite audio. Try another seed.") wave = audio[0].float() peak = float(wave.abs().max()) if peak > 1.0: # peak-normalise instead of hard clipping wave = wave / peak return wave.T.contiguous().numpy() # ---------------------------------------------------------------------------- # Inference # ---------------------------------------------------------------------------- def _estimate_duration(variant=None, style=None, lyrics=None, strength_model=1.0, weirdness_cfg=1.0, score_temperature=0.7, music_temperature=1.0, song_length_cap=240, repetition_penalty=1.2, steps=32, *args, **kwargs): """Fit to two measured ZeroGPU runs so we never over-reserve a visitor's quota. cap=90/cfg=1.0/steps=32 -> 46.5s measured (plan 17.9, semantic 21.7, ode 5.5, vae 1.1) cap=240/cfg=1.4/steps=32 -> 122.9s measured (plan 23.8, semantic 61.7, ode 33.7, vae 2.5) """ cap = float(song_length_cap or 240) cfg = float(weirdness_cfg or 1.0) n_steps = float(steps or 32) plan = 26.0 # planner AR, ~constant semantic = 0.26 * cap # semantic AR, linear in cap ode = cap * (0.055 + 0.00006 * cap) \ * (2.0 if cfg > 1.0 else 1.0) * (n_steps / 32.0) # CFG doubles the NAR work vae = 0.011 * cap seconds = (plan + semantic + ode + vae + 6.0) * 1.15 # +overhead, +15% margin return int(min(290, max(60, round(seconds)))) def _to_mp3(audio: np.ndarray, sample_rate: int): import soundfile as sf out = Path(tempfile.mkdtemp(prefix="mltnt-")) flac_path = out / "mltnt-song.flac" mp3_path = out / "mltnt-song.mp3" sf.write(flac_path, audio, sample_rate, subtype="PCM_24") try: subprocess.run( ["ffmpeg", "-hide_banner", "-loglevel", "error", "-y", "-i", str(flac_path), "-codec:a", "libmp3lame", "-b:a", "192k", str(mp3_path)], check=True, capture_output=True, text=True, ) except (OSError, subprocess.CalledProcessError): return str(flac_path), str(flac_path) return str(mp3_path), str(flac_path) @spaces.GPU(duration=_estimate_duration) def generate( variant: str, style: str, lyrics: str, strength_model: float = 1.0, weirdness_cfg: float = 1.0, score_temperature: float = 0.7, music_temperature: float = 1.0, song_length_cap: int = 240, repetition_penalty: float = 1.2, steps: int = 32, seed: int = 7, strength_clip: float = 1.0, score_mode: str = "full", progress=gr.Progress(), ): """Generate a militant roots reggae song with a MLTNT LoRA on YuE2-3B. Args: variant: Which MLTNT LoRA to merge (Steppers, Frontline, Fusion, Roots). style: One descriptive sentence starting with the trigger word `mltnt,`. lyrics: Tagged lyric blocks — [Verse], [Pre-Chorus], [Chorus], [Bridge], [Outro]. strength_model: LoRA strength on the flow-matching decoder (the sound). weirdness_cfg: Classifier-free guidance on the decoder. 1.0 clean, 1.4 Fusion. score_temperature: Sampling temperature for the ABC score planner. music_temperature: Sampling temperature for the semantic codec tokens. song_length_cap: Maximum song length in seconds. repetition_penalty: Repetition penalty for the semantic stage. steps: Flow-matching ODE steps. seed: Random seed. strength_clip: LoRA strength on the planner. Above 1.0 collapses the vocal. score_mode: `full` writes a chord-annotated score, `melody` a melody-only score, `off` skips the score and goes straight to audio. Returns: An MP3 preview, the FLAC master, the ABC score and a run report. """ style = (style or "").strip() lyrics = (lyrics or "").strip() if not style: raise gr.Error("Write a style prompt. It should start with the trigger word `mltnt,`.") if not lyrics: raise gr.Error("Write lyrics with section tags such as [Verse] and [Chorus].") if len(style) > 2000 or len(lyrics) > 12000: raise gr.Error("Keep the style prompt under 2,000 and the lyrics under 12,000 characters.") stem = VARIANTS.get(variant, VARIANTS[DEFAULT_VARIANT]) strength_clip = float(min(max(strength_clip, 0.0), 1.0)) # >1.0 collapses the vocal strength_model = float(min(max(strength_model, 0.0), 2.0)) weirdness_cfg = float(min(max(weirdness_cfg, 1.0), 3.0)) cap = int(min(max(int(song_length_cap), 60), 360)) steps = int(min(max(int(steps), 8), 64)) seed = int(seed) % (2 ** 31) max_tokens = max(200, cap * FPS) def report(fraction, desc): try: progress(fraction, desc=desc) except Exception: pass started = time.perf_counter() with _LOCK: report(0.0, f"Merging {stem} (clip {strength_clip} / model {strength_model})") merge_seconds = _ensure_merged(stem, strength_model, strength_clip) abc_sampling = replace(pipe.generation_config.abc, temperature=float(score_temperature)) semantic_sampling = Sampling( temperature=float(music_temperature), top_p=0.95, top_k=100, repetition_penalty=float(repetition_penalty), penalty_window=50, min_tokens=200, max_tokens=max_tokens, ) try: planned = {"n": 0} def on_abc(_phase, _token): planned["n"] += 1 if planned["n"] % 64 == 0: report(min(0.18, 0.18 * planned["n"] / 2500), f"Planning the score — {planned['n']} tokens") plan_started = time.perf_counter() plan = pipe.plan( style=style, lyrics=lyrics, cot=score_mode, seed=seed, abc_sampling=abc_sampling, on_token=on_abc, ) plan_seconds = time.perf_counter() - plan_started sung = {"n": 0} def on_semantic(_phase, _token): sung["n"] += 1 if sung["n"] % 64 == 0: report(0.18 + 0.37 * min(1.0, sung["n"] / max_tokens), f"Writing the song — {sung['n'] / FPS:.0f}s of music") semantic_started = time.perf_counter() semantic = pipe.generate_semantic( plan, sampling=semantic_sampling, on_token=on_semantic, ) semantic_seconds = time.perf_counter() - semantic_started if not semantic.tokens: raise gr.Error("The planner produced no music tokens. Try another seed.") def on_ode(done, total): report(0.55 + 0.35 * (done / max(total, 1)), "Rendering the mix") nar_started = time.perf_counter() latents = _synthesize( plan.prefix, semantic.tokens, seed, steps, pipe.generation_config.context, weirdness_cfg, on_progress=on_ode, ) nar_seconds = time.perf_counter() - nar_started report(0.9, "Decoding audio") vae_started = time.perf_counter() audio = _decode(latents.float().cpu().numpy()) vae_seconds = time.perf_counter() - vae_started except gr.Error: raise except torch.cuda.OutOfMemoryError as exc: torch.cuda.empty_cache() raise gr.Error("Ran out of GPU memory. Lower the song length cap.") from exc except Exception as exc: raise gr.Error(f"Generation failed: {type(exc).__name__}: {exc}") from exc total_seconds = time.perf_counter() - started mp3_path, flac_path = _to_mp3(audio, 48000) report = { "lora": f"{stem}.safetensors", "strength_clip_planner": strength_clip, "strength_model_decoder": strength_model, "weirdness_cfg": weirdness_cfg, "score_mode": score_mode, "score_temperature": float(score_temperature), "music_temperature": float(music_temperature), "repetition_penalty": float(repetition_penalty), "ode_steps": steps, "seed": seed, "song_length_cap_s": cap, "audio_seconds": round(len(audio) / 48000, 1), "truncated": bool(plan.truncated or semantic.truncated), "timing_seconds": { "lora_merge": round(merge_seconds, 1), "score_plan": round(plan_seconds, 1), "semantic": round(semantic_seconds, 1), "decoder_ode": round(nar_seconds, 1), "vae": round(vae_seconds, 1), "total": round(total_seconds, 1), }, } score = plan.abc or "score_mode was `off`, so no symbolic score was written." return mp3_path, flac_path, score, json.dumps(report, indent=2) # ---------------------------------------------------------------------------- # Author's own style prompts (from becausereasons/yue2-mltnt-militant-reggae) # ---------------------------------------------------------------------------- STYLE_STEPPERS = ( "mltnt, modern militant roots reggae with massive sub bass drops and dub impact hits, " "dark raspy male vocal, heavy patois delivery, steppers groove, deep bassline, reggae, ska, " "rock, pop rock guitar, hammond organ bubble, nyabinghi percussion, horn stabs, dub sirens, " "spring reverb, tape delay, sparse hard hitting arrangement, conscious lyrics about technology " "outrunning wisdom, forgotten ancestors, tower of babylon rising and falling, anthemic explosive " "chorus, more sub bass, harder drum impact, militant mix, darker vocal tone, more dub drops, " "less sweetness, heavier low end, more space before chorus, explosive chorus entry" ) STYLE_NATIVE = ( "mltnt, Jamaican Patois English, modern militant roots reggae, dark raspy male vocal with heavy " "patois delivery, deep bassline with massive sub bass drops, skanking guitar, hammond organ " "bubble, nyabinghi percussion, horn stabs and dub sirens, apocalyptic conscious mood, 76 BPM, " "sparse hard-hitting arrangement, dub impact hits, harder drum impact, heavier low end, spring " "reverb and tape delay, space before an explosive anthemic chorus" ) STYLE_FUSION = ( "mltnt, Jamaican Patois English, reggae hip-hop fusion, gritty raspy male deejay toasting with " "rapid militant flow, sampled vintage roots vocal hook on the chorus, hard boom-bap drums over a " "one-drop pulse, massive sub bass, sparse skank guitar, dub siren and vinyl crackle, menacing " "urban mood, 78 BPM, big spacious mix with tape delay throws and an explosive chorus" ) STYLE_TRAP_DUB = ( "mltnt, Jamaican Patois English, militant roots reggae fused with trap and dubstep low end, dark " "raspy male deejay switching between rapid-fire chant and gritty half-sung hooks, gang-vocal " "chorus with gospel choir stacks, distorted 808 sub drops and dub impact hits, nyabinghi drums " "under trap hi-hats, halftime breakdowns that snap back into a steppers march, cinematic risers " "and tape-stop drops before every chorus, apocalyptic conscious mood, 80 BPM, cavernous reverb " "and stuttering delay throws, explosive chorus entry" ) STYLE_HIPHOP_DROPS = ( "mltnt, Jamaican Patois English, hard boom-bap hip-hop with a dancehall edge, animated gravelly male " "voice with a wild elastic rapid-fire double-time deejay flow on the verses, growled ad-libs and " "shouted call-and-response, big chanted gang-vocal hook on the chorus, deep 808 sub bass drops that " "fall out and slam back in, heavy kick and cracking snare, dark brass stabs, sparse menacing synth, " "dub siren and tape-stop drops before the chorus, explosive, playful and menacing energy, loud punchy " "club production with huge low end, 96 BPM" ) STYLE_LOVERS_ROCK = ( "mltnt, Jamaican Patois English, lovers rock reggae, smooth tender male tenor vocal with sweet falsetto " "ad-libs and warm female harmony vocals, gentle one drop drums, rounded melodic bassline, clean guitar " "skank, electric piano bubble, soft horn pads, romantic, warm and devotional mood, polished late-night " "studio production with light plate reverb, 72 BPM" ) STYLE_DANCEHALL = ( "mltnt, Jamaican Patois English, hard modern dancehall, gritty commanding male deejay vocal with " "rapid-fire double-time toasting on the verses and a chanted gang-vocal hook, booming 808 sub bass, " "syncopated dembow drum pattern, sparse staccato synth stabs, air horn and siren effects, aggressive, " "defiant sound system energy, loud punchy club production, 100 BPM" ) # Standard sheet: ~8 words a line, four lines a verse, no empty [Intro]. LYRICS_STANDARD = """[Verse] Concrete tower reach up past the cloud Dem a build it pon the bones below Every wire whisper what the elders know Babylon a run but the road run slow [Verse] Satellite a watch the pickney dem play Algorithm teach dem what fi say Mi grandmother drum still a beat the way Roots deeper than the cable dem lay [Pre-Chorus] Wisdom slower than the wire Hold the line [Chorus] Fire pon the tower, let it fall Fire pon the tower, hear we call Man a run the future, lose the past Tower of Babylon never last [Verse] Dem sell yuh a mirror call it a light Trade yuh memory fi a screen so bright Mi tell yuh straight, mi nah sell mi sight Nyabinghi drum a hold the night [Pre-Chorus] Wisdom slower than the wire Hold the line [Chorus] Fire pon the tower, let it fall Fire pon the tower, hear we call Man a run the future, lose the past Tower of Babylon never last [Bridge] Jah know Ancestor a stand inna the doorway Dem never left, dem only wait Every generation get the same key Only the humble one can turn it straight [Outro] Tower of Babylon never last Roots deeper than the cable dem lay """ # Dense sheet: 15-17 words a line, which is what makes the deejay flow rapid-fire. LYRICS_DENSE = """[Verse] Dem seh the future bright but mi seh where the people stand, where the promise and the plan Every tower dem a raise upon the shoulder of a man who never get fi hold the land Satellite a count the breath of every pickney inna yard and file it inna Babylon hard drive Mi grandmother never read the screen but she did read the sky and she still a tell mi how fi stay alive Wire run faster than the wisdom that it carry and the carrying a kill the carrier slow Dem call it progress when the drum get quiet and the elder dem stop teaching what dem know So mi chant it inna patois till the frequency reach the concrete and the concrete start to crack Every word a brick removed from Babylon wall and every brick we take we never giving back [Pre-Chorus] Wisdom slower than the wire Hold the line [Chorus] Fire pon the tower, let it fall Fire pon the tower, hear we call Man a run the future, lose the past Tower of Babylon never last [Verse] Dem sell yuh a mirror and dem call it a light and yuh trade away yuh sight fi the glow Memory outsourced to a server inna desert where the water done and nobody know Mi nah fight the machine, mi a fight the hand that hold it and forget the hand that feed Nyabinghi drum a older than the empire and the drum still a beat the only creed Count the generation, every one of dem receive the exact same key inna dem palm Only the humble one can turn it, everybody else just turn it inna alarm So when the siren drop and the sub bass shake the foundation of the thing dem build so tall Remember seh the ground was always ours, and ground is what a catch dem when dem fall [Pre-Chorus] Wisdom slower than the wire Hold the line [Chorus] Fire pon the tower, let it fall Fire pon the tower, hear we call Man a run the future, lose the past Tower of Babylon never last [Bridge] Jah know Ancestor a stand inna the doorway of the house that dem did build with dem own hand Dem never left the yard, dem only waiting fi the living fi remember how fi stand [Outro] Tower of Babylon never last Roots deeper than the cable dem lay """ CSS = """ #title-block h1 { margin-bottom: 0.2em; } .small-note { font-size: 0.9em; opacity: 0.8; } """ with gr.Blocks(title="MLTNT — Militant Roots Reggae for YuE2") as demo: gr.Markdown( """ # 🔥 MLTNT — Militant Roots Reggae LoRAs for YuE2 Six artist-style LoRAs by **[becausereasons](https://huggingface.co/becausereasons/yue2-mltnt-militant-reggae)** that push **[YuE2-3B](https://huggingface.co/m-a-p/YuE2-3B)** into modern militant roots reggae — dark raspy patois vocals, steppers grooves, deep sub bass, bubbling Hammond, nyabinghi drums, horn stabs, dub sirens and spring reverb. Each LoRA patches **both halves** of YuE2: the autoregressive planner that writes the score and the vocal lines, and the flow-matching decoder that makes the sound. Trigger word: **`mltnt`**. **New: Soundclash and Chanter**, trained with a different trainer. The voice is far more expressive, and they need one habit: **planner strength 0.5** for fast or hard styles, and a **360 s cap**. Picking one of them sets both for you. """, elem_id="title-block", ) with gr.Row(): with gr.Column(scale=5): variant = gr.Dropdown( label="LoRA variant", choices=list(VARIANTS), value=DEFAULT_VARIANT, ) style = gr.Textbox( label="Style prompt", info="One descriptive sentence, trigger first: language → genre → vocal → instruments → mood → BPM → production.", value=STYLE_STEPPERS, lines=5, ) lyrics = gr.Textbox( label="Lyrics", info="Tagged blocks: [Verse] [Pre-Chorus] [Chorus] [Bridge] [Outro]. No empty [Intro] — the planner writes its own.", value=LYRICS_STANDARD, lines=14, ) with gr.Row(): strength_model = gr.Slider( 0.0, 2.0, value=1.0, step=0.05, label="Decoder strength", info="The lever to push: 1.0 → 1.5 gets darker and rougher.", ) weirdness_cfg = gr.Slider( 1.0, 3.0, value=1.0, step=0.05, label="Weirdness (CFG)", info="Sound only, not the arrangement. 1.4 is the Fusion sweet spot. Roughly doubles render time above 1.0.", ) with gr.Row(): score_temperature = gr.Slider( 0.1, 1.5, value=0.7, step=0.05, label="Score temperature", info="How adventurous the written score is.", ) music_temperature = gr.Slider( 0.1, 1.5, value=1.0, step=0.05, label="Music temperature", info="Tempo grid, key and section lengths.", ) run = gr.Button("Generate song", variant="primary", size="lg") with gr.Accordion("Advanced", open=False): song_length_cap = gr.Slider( 60, 360, value=240, step=10, label="Song length cap (seconds)", info="The planner writes full-length intros, so short caps truncate before the second chorus. Soundclash and Chanter plan 250 to 320 s songs: use 360 for them. Longer caps use more of your ZeroGPU quota.", ) with gr.Row(): steps = gr.Slider(8, 64, value=32, step=1, label="Decoder ODE steps") repetition_penalty = gr.Slider( 1.0, 1.5, value=1.2, step=0.01, label="Repetition penalty", ) with gr.Row(): seed = gr.Number(value=7, precision=0, label="Seed") score_mode = gr.Dropdown( choices=["full", "melody", "off"], value="full", label="Score mode", info="`full` = chord-annotated score first (the trained setting).", ) strength_clip = gr.Slider( 0.0, 1.0, value=1.0, step=0.05, label="Planner strength", info="Capped at 1.0 on purpose — above that the planner writes almost no sung bars. For Soundclash and Chanter use 0.5 on fast or hard styles (hip-hop, dancehall, fusion) and 1.0 on slow songs.", ) with gr.Column(scale=4): audio_out = gr.Audio(label="Song (MP3 preview)", type="filepath") flac_out = gr.File(label="FLAC master (48 kHz, 24-bit)") score_out = gr.Textbox(label="ABC score written by the planner", lines=12, max_lines=24) info_out = gr.Code(label="Run report", language="json") gr.Markdown( """ ### Tips from the model card * **Rapid-fire verses come from the lyric, not the prompt.** Write verse lines at 15–17 words (one- and two-syllable words) and keep the chorus at 7–8; asking for "double-time" in the style prompt does not speed the vocal up. *MLTNT Frontline* is the variant trained for this. * **Planner strength above 1.0 collapses the vocal** — it is capped here. * **Soundclash and Chanter behave differently.** At planner strength 1.0 on a fast style they over-commit: everything gets faster and more frantic, and occasionally the score gets stuck in one section (minutes of groove, no singing). Planner 0.5 keeps the voice and restores the structure. On these two files the phrase "rapid-fire double-time deejay flow" *does* speed the whole song up, so keep it for one section unless you want the entire track double time. The same prompt at 1.0 and 0.5 gives two different songs: try both. The one drop rhythm is not learned by any of the six files. * **Production words** ("boom-bap", "deejay toasting", "steppers") move the tempo grid and key far more than the numeric BPM in the same sentence. * **A seed can end a song early.** That is the seed, not the cap — change it and re-run. """, elem_classes=["small-note"], ) EXAMPLE_INPUTS = [variant, style, lyrics, strength_model, weirdness_cfg, score_temperature, music_temperature, song_length_cap, strength_clip] def generate_recipe(variant, style, lyrics, strength_model, weirdness_cfg, score_temperature, music_temperature, song_length_cap, strength_clip, progress=gr.Progress()): """Run one of the author's recipes: the main controls plus the song length cap and planner strength.""" return generate(variant, style, lyrics, strength_model, weirdness_cfg, score_temperature, music_temperature, song_length_cap=song_length_cap, strength_clip=strength_clip, progress=progress) gr.Examples( label="The author's own recipes", examples=[ # Soundclash, hip-hop bass drops, dense lyric, planner 0.5 (the page's headline demo). ["MLTNT Soundclash — new-trainer generation, most expressive voice: hip-hop, dancehall, lovers rock", STYLE_HIPHOP_DROPS, LYRICS_DENSE, 1.0, 1.0, 0.7, 1.0, 360, 0.5], # Soundclash, lovers rock at full planner strength. ["MLTNT Soundclash — new-trainer generation, most expressive voice: hip-hop, dancehall, lovers rock", STYLE_LOVERS_ROCK, LYRICS_STANDARD, 1.0, 1.0, 0.7, 1.0, 360, 1.0], # Soundclash, hard dancehall, planner 0.5. ["MLTNT Soundclash — new-trainer generation, most expressive voice: hip-hop, dancehall, lovers rock", STYLE_DANCEHALL, LYRICS_STANDARD, 1.0, 1.0, 0.7, 1.0, 360, 0.5], # Chanter on the Frontline recipe: planner 1.0 writes fast steppers ... ["MLTNT Chanter — new-trainer generation, settled signature voice", STYLE_STEPPERS, LYRICS_DENSE, 1.0, 1.0, 0.7, 1.0, 360, 1.0], # ... and the same recipe at planner 0.5 becomes a slow roots tune. ["MLTNT Chanter — new-trainer generation, settled signature voice", STYLE_STEPPERS, LYRICS_DENSE, 1.0, 1.0, 0.7, 1.0, 360, 0.5], # Steppers, baseline recipe, standard 8-word lyric. ["MLTNT Steppers — flagship, hard steppers + anthemic chorus", STYLE_STEPPERS, LYRICS_STANDARD, 1.0, 1.0, 0.7, 1.0, 240, 1.0], # Frontline, baseline recipe, dense rapid-fire lyric. ["MLTNT Frontline — newest, most distinctive voice, best for dense lyrics", STYLE_STEPPERS, LYRICS_DENSE, 1.0, 1.0, 0.7, 1.0, 240, 1.0], # Fusion, Fusion recipe, reggae hip-hop prompt. ["MLTNT Fusion — reggae hip-hop, boom-bap over one-drop", STYLE_FUSION, LYRICS_STANDARD, 1.5, 1.4, 0.9, 1.2, 240, 1.0], # Frontline on the Fusion recipe with the trap-dub wild card. ["MLTNT Frontline — newest, most distinctive voice, best for dense lyrics", STYLE_TRAP_DUB, LYRICS_DENSE, 1.5, 1.4, 0.9, 1.2, 240, 1.0], # Roots, baseline recipe, strict native caption order. ["MLTNT Roots — the purist, straightest roots timbre", STYLE_NATIVE, LYRICS_STANDARD, 1.0, 1.0, 0.7, 1.0, 240, 1.0], ], inputs=EXAMPLE_INPUTS, outputs=[audio_out, flac_out, score_out, info_out], fn=generate_recipe, cache_examples=True, cache_mode="lazy", ) gr.Markdown( """ LoRA weights © becausereasons, **CC BY-NC 4.0** (non-commercial; attribute "MLTNT LoRAs by becausereasons"). Base model [m-a-p/YuE2-3B](https://huggingface.co/m-a-p/YuE2-3B), same licence. The LoRAs were trained with [ComfyUI-FS_Audio_Suite](https://github.com/KytraScript/ComfyUI-FS_Audio_Suite) against Comfy's repack (Soundclash and Chanter with [AI Toolkit](https://github.com/ostris/ai-toolkit)); this Space merges them into the native `yue2_infer` pipeline and reproduces the FS_Audio graph's sampling recipe, including CFG on the flow-matching decoder. """, elem_classes=["small-note"], ) def _suggest_defaults(chosen): """Planner 0.5 and a 360 s cap for the new-trainer files; the original defaults for the others.""" new = VARIANTS.get(chosen) in NEW_TRAINER_STEMS return (gr.update(value=0.5 if new else 1.0), gr.update(value=360 if new else 240)) variant.input(fn=_suggest_defaults, inputs=variant, outputs=[strength_clip, song_length_cap], api_visibility="private", show_progress="hidden") run.click( fn=generate, inputs=[variant, style, lyrics, strength_model, weirdness_cfg, score_temperature, music_temperature, song_length_cap, repetition_penalty, steps, seed, strength_clip, score_mode], outputs=[audio_out, flac_out, score_out, info_out], api_name="generate", concurrency_limit=1, ) if __name__ == "__main__": demo.queue(max_size=20).launch( theme=gr.themes.Citrus(), css=CSS, mcp_server=True, )