22
Browse files- app.py +264 -223
- src/heartlib/pipelines/music_generation.py +191 -2
app.py
CHANGED
|
@@ -17,7 +17,6 @@ import uuid
|
|
| 17 |
import subprocess
|
| 18 |
import tempfile
|
| 19 |
import wave
|
| 20 |
-
import time
|
| 21 |
from pathlib import Path
|
| 22 |
from typing import Dict, Any, Iterator, Optional, Tuple, TYPE_CHECKING
|
| 23 |
|
|
@@ -29,7 +28,15 @@ try:
|
|
| 29 |
except Exception:
|
| 30 |
spaces = None
|
| 31 |
|
| 32 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 33 |
|
| 34 |
|
| 35 |
def _env_bool(name: str) -> Optional[bool]:
|
|
@@ -41,7 +48,7 @@ def _env_bool(name: str) -> Optional[bool]:
|
|
| 41 |
|
| 42 |
_default_keep_model_loaded_env = _env_bool("KEEP_MODEL_LOADED_DEFAULT")
|
| 43 |
if _default_keep_model_loaded_env is None:
|
| 44 |
-
DEFAULT_KEEP_MODEL_LOADED = not
|
| 45 |
else:
|
| 46 |
DEFAULT_KEEP_MODEL_LOADED = _default_keep_model_loaded_env
|
| 47 |
|
|
@@ -59,9 +66,11 @@ sys.path.insert(0, os.path.join(os.path.dirname(__file__), 'src'))
|
|
| 59 |
|
| 60 |
# HF Spaces: disable SSR to avoid Node proxy/port issues.
|
| 61 |
os.environ.setdefault("GRADIO_SSR_MODE", "0")
|
|
|
|
|
|
|
| 62 |
|
| 63 |
if TYPE_CHECKING:
|
| 64 |
-
from heartlib import HeartMuLaGenPipeline
|
| 65 |
import gradio as gr
|
| 66 |
import re
|
| 67 |
from google import genai
|
|
@@ -209,26 +218,100 @@ except Exception as e:
|
|
| 209 |
GRADIO_QUEUE_MAX_SIZE = int(os.environ.get("GRADIO_QUEUE_MAX_SIZE", "24"))
|
| 210 |
GRADIO_DEFAULT_CONCURRENCY = int(os.environ.get("GRADIO_DEFAULT_CONCURRENCY", "1"))
|
| 211 |
GPU_CONCURRENCY_LIMIT = int(os.environ.get("GRADIO_GPU_CONCURRENCY", "1"))
|
| 212 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 213 |
|
| 214 |
|
| 215 |
class ModelManager:
|
| 216 |
def __init__(self, model_path: str, use_deepspeed_override: Optional[bool] = None):
|
| 217 |
import torch
|
| 218 |
-
from heartlib import HeartMuLaGenPipeline
|
| 219 |
|
| 220 |
self.model_path = model_path
|
| 221 |
self.device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
|
| 222 |
self.dtype = torch.bfloat16 if torch.cuda.is_available() else torch.float32
|
| 223 |
self._gen_pipes: Dict[Tuple[str, str, str], "HeartMuLaGenPipeline"] = {}
|
| 224 |
-
self._transcribe_pipe: Optional["HeartTranscriptorPipeline"] = None
|
| 225 |
if use_deepspeed_override is None:
|
| 226 |
self.use_deepspeed = os.getenv("USE_DEEPSPEED_INFERENCE", "0").lower() in ("1", "true", "yes")
|
| 227 |
else:
|
| 228 |
self.use_deepspeed = use_deepspeed_override
|
| 229 |
self.ds_inference_config = self._make_ds_inference_config()
|
| 230 |
self._HeartMuLaGenPipeline = HeartMuLaGenPipeline
|
| 231 |
-
self._HeartTranscriptorPipeline = HeartTranscriptorPipeline
|
| 232 |
|
| 233 |
def _make_ds_inference_config(self) -> Dict[str, Any]:
|
| 234 |
if not self.use_deepspeed:
|
|
@@ -249,6 +332,8 @@ class ModelManager:
|
|
| 249 |
return None
|
| 250 |
if self.device.type != "cuda":
|
| 251 |
raise gr.Error("Quantization requires CUDA.")
|
|
|
|
|
|
|
| 252 |
if quant_mode == "4bit":
|
| 253 |
quant_type = "nf4"
|
| 254 |
try:
|
|
@@ -278,22 +363,13 @@ class ModelManager:
|
|
| 278 |
version=version,
|
| 279 |
codec_version=codec_version,
|
| 280 |
bnb_config=bnb_config,
|
|
|
|
| 281 |
lazy_load=True,
|
| 282 |
use_deepspeed=self.use_deepspeed,
|
| 283 |
ds_inference_config=self.ds_inference_config,
|
| 284 |
)
|
| 285 |
return self._gen_pipes[key]
|
| 286 |
|
| 287 |
-
def get_transcriptor(self) -> "HeartTranscriptorPipeline":
|
| 288 |
-
import torch
|
| 289 |
-
if self._transcribe_pipe is None:
|
| 290 |
-
self._transcribe_pipe = self._HeartTranscriptorPipeline.from_pretrained(
|
| 291 |
-
self.model_path,
|
| 292 |
-
device=self.device,
|
| 293 |
-
dtype=torch.float16 if self.device.type == "cuda" else torch.float32,
|
| 294 |
-
)
|
| 295 |
-
return self._transcribe_pipe
|
| 296 |
-
|
| 297 |
|
| 298 |
model_managers: Dict[str, ModelManager] = {}
|
| 299 |
|
|
@@ -459,50 +535,18 @@ def download_models_if_needed(ckpt_dir):
|
|
| 459 |
print("")
|
| 460 |
|
| 461 |
|
| 462 |
-
def check_transcriptor_exists(ckpt_dir):
|
| 463 |
-
"""Check if HeartTranscriptor checkpoint exists"""
|
| 464 |
-
return os.path.exists(os.path.join(ckpt_dir, "HeartTranscriptor-oss"))
|
| 465 |
-
|
| 466 |
-
|
| 467 |
-
def download_transcriptor_if_needed(ckpt_dir):
|
| 468 |
-
"""Download HeartTranscriptor from ModelScope if not present"""
|
| 469 |
-
if check_transcriptor_exists(ckpt_dir):
|
| 470 |
-
print("=" * 50)
|
| 471 |
-
print(f"✓ HeartTranscriptor found in {ckpt_dir}")
|
| 472 |
-
print("✓ Skipping HeartTranscriptor download")
|
| 473 |
-
print("=" * 50)
|
| 474 |
-
return
|
| 475 |
-
|
| 476 |
-
print("=" * 50)
|
| 477 |
-
print("⬇ Downloading HeartTranscriptor from ModelScope")
|
| 478 |
-
print("=" * 50)
|
| 479 |
-
print("")
|
| 480 |
-
|
| 481 |
-
from modelscope import snapshot_download
|
| 482 |
-
|
| 483 |
-
snapshot_download(
|
| 484 |
-
'HeartMuLa/HeartTranscriptor-oss',
|
| 485 |
-
local_dir=os.path.join(ckpt_dir, 'HeartTranscriptor-oss')
|
| 486 |
-
)
|
| 487 |
-
|
| 488 |
-
print("✓ HeartTranscriptor download completed")
|
| 489 |
-
print("")
|
| 490 |
-
|
| 491 |
-
|
| 492 |
def load_pipeline(model_path, version, codec_version, quant_mode, use_acceleration: bool):
|
| 493 |
"""Load HeartMuLa pipeline (lazy)"""
|
| 494 |
-
if not use_acceleration:
|
| 495 |
-
quant_mode = "none"
|
| 496 |
manager = get_model_manager(use_acceleration)
|
| 497 |
print(f"Using model from {model_path} on {manager.device}...")
|
| 498 |
return manager.get_gen_pipeline(version, codec_version, quant_mode)
|
| 499 |
|
| 500 |
|
| 501 |
-
def
|
| 502 |
-
|
| 503 |
-
|
| 504 |
-
|
| 505 |
-
|
| 506 |
|
| 507 |
|
| 508 |
def generate(
|
|
@@ -523,11 +567,7 @@ def generate(
|
|
| 523 |
):
|
| 524 |
"""Generate music"""
|
| 525 |
import torch
|
| 526 |
-
|
| 527 |
-
raise gr.Error("Please enter lyrics")
|
| 528 |
-
|
| 529 |
-
if not tags.strip():
|
| 530 |
-
raise gr.Error("Please enter tags")
|
| 531 |
|
| 532 |
max_audio_length_ms = int(duration_sec * 1000)
|
| 533 |
|
|
@@ -538,7 +578,7 @@ def generate(
|
|
| 538 |
pipe = load_pipeline(MODEL_PATH, version, codec_version, quant_mode, use_acceleration)
|
| 539 |
output_path = os.path.join(DATA_DIR, f"gen_{uuid.uuid4().hex}.wav")
|
| 540 |
|
| 541 |
-
with torch.
|
| 542 |
pipe(
|
| 543 |
{
|
| 544 |
"lyrics": lyrics,
|
|
@@ -566,6 +606,9 @@ def generate(
|
|
| 566 |
|
| 567 |
except Exception as e:
|
| 568 |
raise gr.Error(f"Generation error: {str(e)}")
|
|
|
|
|
|
|
|
|
|
| 569 |
|
| 570 |
|
| 571 |
@_gpu_guard
|
|
@@ -636,37 +679,6 @@ def generate_accelerated(
|
|
| 636 |
)
|
| 637 |
|
| 638 |
|
| 639 |
-
@_gpu_guard
|
| 640 |
-
def transcribe_audio(audio_path, task, max_new_tokens, num_beams, temperature):
|
| 641 |
-
"""Transcribe or translate lyrics from audio"""
|
| 642 |
-
import torch
|
| 643 |
-
if not audio_path:
|
| 644 |
-
raise gr.Error("Please upload an audio file")
|
| 645 |
-
|
| 646 |
-
pipe = load_transcriptor(MODEL_PATH)
|
| 647 |
-
|
| 648 |
-
try:
|
| 649 |
-
with torch.no_grad():
|
| 650 |
-
result = pipe(
|
| 651 |
-
audio_path,
|
| 652 |
-
**{
|
| 653 |
-
"max_new_tokens": int(max_new_tokens),
|
| 654 |
-
"num_beams": int(num_beams),
|
| 655 |
-
"task": task,
|
| 656 |
-
"condition_on_prev_tokens": False,
|
| 657 |
-
"compression_ratio_threshold": 1.8,
|
| 658 |
-
"temperature": float(temperature),
|
| 659 |
-
"logprob_threshold": -1.0,
|
| 660 |
-
"no_speech_threshold": 0.4,
|
| 661 |
-
},
|
| 662 |
-
)
|
| 663 |
-
if isinstance(result, dict):
|
| 664 |
-
return result.get("text", "")
|
| 665 |
-
return str(result)
|
| 666 |
-
except Exception as e:
|
| 667 |
-
raise gr.Error(f"Transcription error: {str(e)}")
|
| 668 |
-
|
| 669 |
-
|
| 670 |
def _normalize_stream_chunk(chunk: np.ndarray) -> np.ndarray:
|
| 671 |
chunk = np.nan_to_num(chunk, nan=0.0, posinf=0.0, neginf=0.0)
|
| 672 |
return np.clip(chunk, -1.0, 1.0)
|
|
@@ -685,9 +697,9 @@ def generate_music_streaming(
|
|
| 685 |
keep_model_loaded,
|
| 686 |
offload_mode,
|
| 687 |
backend,
|
| 688 |
-
chunk_frames,
|
| 689 |
use_acceleration,
|
| 690 |
) -> Iterator[Tuple[int, np.ndarray]]:
|
|
|
|
| 691 |
if backend == "exllama_v2":
|
| 692 |
raise gr.Error("ExLlamaV2 backend is not implemented yet.")
|
| 693 |
pipe = load_pipeline(MODEL_PATH, version, codec_version, quant_mode, use_acceleration)
|
|
@@ -698,7 +710,7 @@ def generate_music_streaming(
|
|
| 698 |
temperature=temperature,
|
| 699 |
topk=topk,
|
| 700 |
cfg_scale=cfg_scale,
|
| 701 |
-
chunk_frames=
|
| 702 |
keep_model_loaded=keep_model_loaded,
|
| 703 |
offload_mode=offload_mode,
|
| 704 |
):
|
|
@@ -706,8 +718,8 @@ def generate_music_streaming(
|
|
| 706 |
chunk = chunk.squeeze(0)
|
| 707 |
chunk_np = chunk.cpu().numpy()
|
| 708 |
chunk_np = _normalize_stream_chunk(chunk_np)
|
| 709 |
-
print(f"stream chunk: samples={chunk_np.shape[0]} sr=
|
| 710 |
-
yield
|
| 711 |
|
| 712 |
|
| 713 |
def stream_generate(
|
|
@@ -723,22 +735,14 @@ def stream_generate(
|
|
| 723 |
keep_model_loaded,
|
| 724 |
offload_mode,
|
| 725 |
backend,
|
| 726 |
-
output_format,
|
| 727 |
-
chunk_frames,
|
| 728 |
use_acceleration,
|
| 729 |
):
|
| 730 |
try:
|
| 731 |
-
|
| 732 |
-
|
| 733 |
-
|
| 734 |
-
|
| 735 |
-
print(
|
| 736 |
-
"stream start:",
|
| 737 |
-
f"min_chunk_sec={STREAM_MIN_CHUNK_SEC}",
|
| 738 |
-
f"duration_sec={duration_sec}",
|
| 739 |
-
f"chunk_frames={chunk_frames}",
|
| 740 |
-
)
|
| 741 |
-
|
| 742 |
for sr, chunk_np in generate_music_streaming(
|
| 743 |
lyrics=lyrics,
|
| 744 |
tags=tags,
|
|
@@ -752,26 +756,19 @@ def stream_generate(
|
|
| 752 |
keep_model_loaded=keep_model_loaded,
|
| 753 |
offload_mode=offload_mode,
|
| 754 |
backend=backend,
|
| 755 |
-
chunk_frames=chunk_frames,
|
| 756 |
use_acceleration=use_acceleration,
|
| 757 |
):
|
| 758 |
chunk_np = chunk_np.astype("float32", copy=False)
|
| 759 |
-
|
| 760 |
-
|
| 761 |
-
yield sr, chunk_np
|
| 762 |
-
continue
|
| 763 |
-
buffer.append(chunk_np)
|
| 764 |
-
buffered_samples += chunk_np.shape[0]
|
| 765 |
-
if buffered_samples - last_yield_samples < min_samples:
|
| 766 |
continue
|
| 767 |
-
|
| 768 |
-
|
| 769 |
-
|
| 770 |
-
|
| 771 |
-
|
| 772 |
-
|
| 773 |
-
|
| 774 |
-
yield 48000, full_audio
|
| 775 |
except Exception as e:
|
| 776 |
raise gr.Error(f"Streaming error: {str(e)}")
|
| 777 |
|
|
@@ -790,8 +787,6 @@ def stream_generate_accelerated(
|
|
| 790 |
keep_model_loaded,
|
| 791 |
offload_mode,
|
| 792 |
backend,
|
| 793 |
-
output_format,
|
| 794 |
-
chunk_frames,
|
| 795 |
):
|
| 796 |
return stream_generate(
|
| 797 |
lyrics,
|
|
@@ -806,8 +801,6 @@ def stream_generate_accelerated(
|
|
| 806 |
keep_model_loaded,
|
| 807 |
offload_mode,
|
| 808 |
backend,
|
| 809 |
-
output_format,
|
| 810 |
-
chunk_frames,
|
| 811 |
True,
|
| 812 |
)
|
| 813 |
|
|
@@ -977,10 +970,15 @@ Output ONLY the lyrics with structure tags, no explanations.
|
|
| 977 |
|
| 978 |
def create_ui():
|
| 979 |
"""Create Gradio UI"""
|
|
|
|
|
|
|
|
|
|
|
|
|
| 980 |
|
| 981 |
with gr.Blocks(title="HeartMuLa Music Generation") as demo:
|
| 982 |
gr.Markdown("# HeartMuLa Music Generation")
|
| 983 |
gr.Markdown("Generate music from lyrics and style tags")
|
|
|
|
| 984 |
|
| 985 |
with gr.Tabs():
|
| 986 |
# Tab 1: Music Generation
|
|
@@ -1030,10 +1028,19 @@ def create_ui():
|
|
| 1030 |
t7 = gr.CheckboxGroup(choices=TAG_DATA["Topic"], label="Topic")
|
| 1031 |
tag_checkboxes.append(t7)
|
| 1032 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1033 |
# Generation parameters
|
| 1034 |
with gr.Row():
|
| 1035 |
cfg_scale = gr.Slider(0.0, 3.0, value=1.5, step=0.1, label="CFG Scale")
|
| 1036 |
-
duration = gr.Slider(10, 300, value=
|
| 1037 |
|
| 1038 |
with gr.Row():
|
| 1039 |
temperature = gr.Slider(0.1, 2.0, value=1.0, step=0.1, label="Temperature")
|
|
@@ -1056,9 +1063,14 @@ def create_ui():
|
|
| 1056 |
label="Codec Version"
|
| 1057 |
)
|
| 1058 |
quant_mode = gr.Dropdown(
|
| 1059 |
-
choices=[
|
| 1060 |
-
|
| 1061 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1062 |
)
|
| 1063 |
keep_model_loaded = gr.Checkbox(
|
| 1064 |
value=DEFAULT_KEEP_MODEL_LOADED,
|
|
@@ -1066,43 +1078,76 @@ def create_ui():
|
|
| 1066 |
)
|
| 1067 |
offload_mode = gr.Dropdown(
|
| 1068 |
choices=["auto", "aggressive"],
|
| 1069 |
-
value=
|
| 1070 |
label="Offload Mode"
|
| 1071 |
)
|
| 1072 |
output_format = gr.Radio(
|
| 1073 |
choices=[("WAV", "wav"), ("MP3", "mp3")],
|
| 1074 |
value="wav",
|
| 1075 |
-
label="Output Format"
|
|
|
|
| 1076 |
)
|
| 1077 |
-
|
| 1078 |
-
|
|
|
|
| 1079 |
)
|
| 1080 |
|
| 1081 |
gr.Markdown("### 🚀 Generation")
|
| 1082 |
|
| 1083 |
generation_mode = gr.Radio(
|
| 1084 |
choices=["Original (No Acceleration)", "Accelerated"],
|
| 1085 |
-
value=
|
| 1086 |
label="Generation Mode",
|
| 1087 |
)
|
| 1088 |
|
| 1089 |
speed_submode = gr.Radio(
|
| 1090 |
-
choices=[
|
| 1091 |
-
|
|
|
|
|
|
|
|
|
|
| 1092 |
label="Accelerated Options",
|
| 1093 |
-
visible=
|
| 1094 |
)
|
| 1095 |
|
| 1096 |
-
btn_original = gr.Button(
|
| 1097 |
-
|
| 1098 |
-
|
| 1099 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1100 |
cancel_state = gr.State()
|
| 1101 |
|
| 1102 |
with gr.Column():
|
| 1103 |
# Notice section
|
| 1104 |
with gr.Accordion("Usage Notice", open=True):
|
| 1105 |
gr.Markdown("""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1106 |
### Lyrics Format Requirements
|
| 1107 |
|
| 1108 |
**Automatic Processing:**
|
|
@@ -1142,14 +1187,16 @@ Every day the fire burns
|
|
| 1142 |
label="Generated Music (Full)",
|
| 1143 |
type="filepath",
|
| 1144 |
interactive=False,
|
|
|
|
| 1145 |
)
|
| 1146 |
stream_audio = gr.Audio(
|
| 1147 |
-
label="Streaming Audio (Preview)",
|
| 1148 |
streaming=True,
|
| 1149 |
autoplay=True,
|
| 1150 |
type="numpy",
|
| 1151 |
format="wav",
|
| 1152 |
interactive=False,
|
|
|
|
| 1153 |
)
|
| 1154 |
|
| 1155 |
# Event handlers for tag selection
|
|
@@ -1171,6 +1218,9 @@ Every day the fire burns
|
|
| 1171 |
gr.update(visible=False), # btn_accel
|
| 1172 |
gr.update(visible=False), # btn_stream
|
| 1173 |
gr.update(visible=False), # cancel_stream_btn
|
|
|
|
|
|
|
|
|
|
| 1174 |
)
|
| 1175 |
show_stream = spd_mode == "Streaming"
|
| 1176 |
return (
|
|
@@ -1179,17 +1229,83 @@ Every day the fire burns
|
|
| 1179 |
gr.update(visible=not show_stream), # btn_accel
|
| 1180 |
gr.update(visible=show_stream), # btn_stream
|
| 1181 |
gr.update(visible=show_stream), # cancel_stream_btn
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1182 |
)
|
| 1183 |
|
| 1184 |
generation_mode.change(
|
| 1185 |
fn=update_visibility,
|
| 1186 |
inputs=[generation_mode, speed_submode],
|
| 1187 |
-
outputs=[
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1188 |
)
|
| 1189 |
speed_submode.change(
|
| 1190 |
fn=update_visibility,
|
| 1191 |
inputs=[generation_mode, speed_submode],
|
| 1192 |
-
outputs=[
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1193 |
)
|
| 1194 |
|
| 1195 |
btn_original.click(
|
|
@@ -1251,8 +1367,6 @@ Every day the fire burns
|
|
| 1251 |
keep_model_loaded,
|
| 1252 |
offload_mode,
|
| 1253 |
backend,
|
| 1254 |
-
output_format,
|
| 1255 |
-
chunk_frames,
|
| 1256 |
],
|
| 1257 |
outputs=[stream_audio],
|
| 1258 |
concurrency_id="gpu_queue",
|
|
@@ -1397,81 +1511,8 @@ Every day the fire burns
|
|
| 1397 |
outputs=[lyrics]
|
| 1398 |
)
|
| 1399 |
|
| 1400 |
-
# Tab 3: Lyrics Transcription
|
| 1401 |
-
with gr.Tab("Lyrics Transcription"):
|
| 1402 |
-
with gr.Row():
|
| 1403 |
-
with gr.Column():
|
| 1404 |
-
gr.Markdown("### Transcribe or Translate Lyrics from Audio")
|
| 1405 |
-
|
| 1406 |
-
audio_input = gr.Audio(
|
| 1407 |
-
label="Audio Input",
|
| 1408 |
-
type="filepath"
|
| 1409 |
-
)
|
| 1410 |
-
|
| 1411 |
-
task_select = gr.Radio(
|
| 1412 |
-
choices=[
|
| 1413 |
-
("Transcribe (Original Language)", "transcribe"),
|
| 1414 |
-
("Translate to English", "translate")
|
| 1415 |
-
],
|
| 1416 |
-
value="transcribe",
|
| 1417 |
-
label="Task"
|
| 1418 |
-
)
|
| 1419 |
-
|
| 1420 |
-
max_new_tokens = gr.Slider(
|
| 1421 |
-
64, 512, value=256, step=16, label="Max New Tokens"
|
| 1422 |
-
)
|
| 1423 |
-
num_beams = gr.Slider(
|
| 1424 |
-
1, 5, value=2, step=1, label="Beam Search"
|
| 1425 |
-
)
|
| 1426 |
-
transcribe_temperature = gr.Slider(
|
| 1427 |
-
0.0, 0.8, value=0.2, step=0.1, label="Temperature"
|
| 1428 |
-
)
|
| 1429 |
-
|
| 1430 |
-
transcribe_btn = gr.Button(
|
| 1431 |
-
"Run Transcription",
|
| 1432 |
-
variant="primary",
|
| 1433 |
-
size="lg"
|
| 1434 |
-
)
|
| 1435 |
-
|
| 1436 |
-
use_generated_audio = gr.Button(
|
| 1437 |
-
"Use Generated Music",
|
| 1438 |
-
size="sm"
|
| 1439 |
-
)
|
| 1440 |
-
|
| 1441 |
-
with gr.Column():
|
| 1442 |
-
with gr.Accordion("Notes", open=True):
|
| 1443 |
-
gr.Markdown("""
|
| 1444 |
-
### Notes
|
| 1445 |
-
- Best results come from **vocals-only** stems.
|
| 1446 |
-
- If you pass full mixes, consider source separation first.
|
| 1447 |
-
- The HeartTranscriptor model will auto-download on first use.
|
| 1448 |
-
""")
|
| 1449 |
-
|
| 1450 |
-
transcription_output = gr.Textbox(
|
| 1451 |
-
label="Transcription Result",
|
| 1452 |
-
lines=18,
|
| 1453 |
-
placeholder="Transcribed lyrics will appear here...",
|
| 1454 |
-
interactive=False
|
| 1455 |
-
)
|
| 1456 |
-
|
| 1457 |
-
transcribe_btn.click(
|
| 1458 |
-
fn=transcribe_audio,
|
| 1459 |
-
inputs=[audio_input, task_select, max_new_tokens, num_beams, transcribe_temperature],
|
| 1460 |
-
outputs=[transcription_output],
|
| 1461 |
-
concurrency_id="gpu_queue",
|
| 1462 |
-
concurrency_limit=GPU_CONCURRENCY_LIMIT,
|
| 1463 |
-
)
|
| 1464 |
-
|
| 1465 |
-
use_generated_audio.click(
|
| 1466 |
-
fn=lambda x: x,
|
| 1467 |
-
inputs=[output_audio_file],
|
| 1468 |
-
outputs=[audio_input]
|
| 1469 |
-
)
|
| 1470 |
-
|
| 1471 |
return demo
|
| 1472 |
|
| 1473 |
-
|
| 1474 |
-
demo = create_ui()
|
| 1475 |
demo.queue(max_size=GRADIO_QUEUE_MAX_SIZE, default_concurrency_limit=GRADIO_DEFAULT_CONCURRENCY)
|
| 1476 |
|
| 1477 |
if __name__ == "__main__":
|
|
|
|
| 17 |
import subprocess
|
| 18 |
import tempfile
|
| 19 |
import wave
|
|
|
|
| 20 |
from pathlib import Path
|
| 21 |
from typing import Dict, Any, Iterator, Optional, Tuple, TYPE_CHECKING
|
| 22 |
|
|
|
|
| 28 |
except Exception:
|
| 29 |
spaces = None
|
| 30 |
|
| 31 |
+
IS_SPACE = spaces is not None and os.environ.get("SPACE_ID")
|
| 32 |
+
|
| 33 |
+
_gpu_duration_env = os.environ.get("GPU_MAX_DURATION")
|
| 34 |
+
if _gpu_duration_env is None:
|
| 35 |
+
aoti_env = os.environ.get("ENABLE_AOTI")
|
| 36 |
+
enable_aoti_default = aoti_env is None or aoti_env.strip().lower() in ("1", "true", "yes", "y", "on")
|
| 37 |
+
GPU_MAX_DURATION = 600 if IS_SPACE and enable_aoti_default else 100
|
| 38 |
+
else:
|
| 39 |
+
GPU_MAX_DURATION = int(_gpu_duration_env)
|
| 40 |
|
| 41 |
|
| 42 |
def _env_bool(name: str) -> Optional[bool]:
|
|
|
|
| 48 |
|
| 49 |
_default_keep_model_loaded_env = _env_bool("KEEP_MODEL_LOADED_DEFAULT")
|
| 50 |
if _default_keep_model_loaded_env is None:
|
| 51 |
+
DEFAULT_KEEP_MODEL_LOADED = not IS_SPACE
|
| 52 |
else:
|
| 53 |
DEFAULT_KEEP_MODEL_LOADED = _default_keep_model_loaded_env
|
| 54 |
|
|
|
|
| 66 |
|
| 67 |
# HF Spaces: disable SSR to avoid Node proxy/port issues.
|
| 68 |
os.environ.setdefault("GRADIO_SSR_MODE", "0")
|
| 69 |
+
# Mitigate CUDA memory fragmentation on small GPUs.
|
| 70 |
+
os.environ.setdefault("PYTORCH_CUDA_ALLOC_CONF", "expandable_segments:True")
|
| 71 |
|
| 72 |
if TYPE_CHECKING:
|
| 73 |
+
from heartlib import HeartMuLaGenPipeline
|
| 74 |
import gradio as gr
|
| 75 |
import re
|
| 76 |
from google import genai
|
|
|
|
| 218 |
GRADIO_QUEUE_MAX_SIZE = int(os.environ.get("GRADIO_QUEUE_MAX_SIZE", "24"))
|
| 219 |
GRADIO_DEFAULT_CONCURRENCY = int(os.environ.get("GRADIO_DEFAULT_CONCURRENCY", "1"))
|
| 220 |
GPU_CONCURRENCY_LIMIT = int(os.environ.get("GRADIO_GPU_CONCURRENCY", "1"))
|
| 221 |
+
DEFAULT_DURATION_SEC = int(os.environ.get("DEFAULT_DURATION_SEC", "60" if IS_SPACE else "180"))
|
| 222 |
+
DEFAULT_QUANT_MODE = os.environ.get("DEFAULT_QUANT_MODE", "4bit" if IS_SPACE else "none")
|
| 223 |
+
DEFAULT_OFFLOAD_MODE = os.environ.get("DEFAULT_OFFLOAD_MODE", "aggressive" if IS_SPACE else "auto")
|
| 224 |
+
DEFAULT_GENERATION_MODE = os.environ.get(
|
| 225 |
+
"DEFAULT_GENERATION_MODE",
|
| 226 |
+
"Accelerated" if IS_SPACE else "Original (No Acceleration)",
|
| 227 |
+
)
|
| 228 |
+
DEFAULT_SPEED_SUBMODE = os.environ.get("DEFAULT_SPEED_SUBMODE", "Standard")
|
| 229 |
+
DEFAULT_PRESET = os.environ.get("DEFAULT_PRESET", "ZeroGPU Safe" if IS_SPACE else "Balanced")
|
| 230 |
+
MODEL_DETOKENIZE_INTERVAL_SEC = float(os.environ.get("MODEL_DETOKENIZE_INTERVAL_SEC", "29.76"))
|
| 231 |
+
AUDIO_SAMPLE_RATE = int(os.environ.get("AUDIO_SAMPLE_RATE", "48000"))
|
| 232 |
+
FRAME_MS = 80.0
|
| 233 |
+
BLOCK_FRAMES = max(1, int(round((MODEL_DETOKENIZE_INTERVAL_SEC * 1000.0) / FRAME_MS)))
|
| 234 |
+
BLOCK_SAMPLES = max(1, int(round(MODEL_DETOKENIZE_INTERVAL_SEC * AUDIO_SAMPLE_RATE)))
|
| 235 |
+
MIN_BUFFER_BLOCKS = int(os.environ.get("MIN_BUFFER_BLOCKS", "1"))
|
| 236 |
+
PREFETCH_BLOCKS = int(os.environ.get("PREFETCH_BLOCKS", "2"))
|
| 237 |
+
MAX_QUEUE_BLOCKS = int(os.environ.get("MAX_QUEUE_BLOCKS", "4"))
|
| 238 |
+
|
| 239 |
+
PRESET_CONFIGS = {
|
| 240 |
+
"ZeroGPU Safe": {
|
| 241 |
+
"duration": 60,
|
| 242 |
+
"quant_mode": "4bit",
|
| 243 |
+
"offload_mode": "aggressive",
|
| 244 |
+
"keep_model_loaded": False,
|
| 245 |
+
"temperature": 1.0,
|
| 246 |
+
"topk": 50,
|
| 247 |
+
"cfg_scale": 1.5,
|
| 248 |
+
"generation_mode": "Accelerated",
|
| 249 |
+
"speed_submode": "Standard",
|
| 250 |
+
},
|
| 251 |
+
"ZeroGPU AOTI FP8": {
|
| 252 |
+
"duration": 60,
|
| 253 |
+
"quant_mode": "fp8",
|
| 254 |
+
"offload_mode": "aggressive",
|
| 255 |
+
"keep_model_loaded": False,
|
| 256 |
+
"temperature": 1.0,
|
| 257 |
+
"topk": 50,
|
| 258 |
+
"cfg_scale": 1.5,
|
| 259 |
+
"generation_mode": "Accelerated",
|
| 260 |
+
"speed_submode": "Standard",
|
| 261 |
+
},
|
| 262 |
+
"Balanced": {
|
| 263 |
+
"duration": 120,
|
| 264 |
+
"quant_mode": "4bit" if IS_SPACE else "none",
|
| 265 |
+
"offload_mode": "auto",
|
| 266 |
+
"keep_model_loaded": DEFAULT_KEEP_MODEL_LOADED,
|
| 267 |
+
"temperature": 1.0,
|
| 268 |
+
"topk": 50,
|
| 269 |
+
"cfg_scale": 1.5,
|
| 270 |
+
"generation_mode": "Accelerated",
|
| 271 |
+
"speed_submode": "Standard",
|
| 272 |
+
},
|
| 273 |
+
"Quality": {
|
| 274 |
+
"duration": 180,
|
| 275 |
+
"quant_mode": "none",
|
| 276 |
+
"offload_mode": "auto",
|
| 277 |
+
"keep_model_loaded": DEFAULT_KEEP_MODEL_LOADED,
|
| 278 |
+
"temperature": 0.9,
|
| 279 |
+
"topk": 50,
|
| 280 |
+
"cfg_scale": 2.0,
|
| 281 |
+
"generation_mode": "Original (No Acceleration)",
|
| 282 |
+
"speed_submode": "Standard",
|
| 283 |
+
},
|
| 284 |
+
}
|
| 285 |
+
if DEFAULT_PRESET not in PRESET_CONFIGS:
|
| 286 |
+
DEFAULT_PRESET = "Balanced"
|
| 287 |
+
_default_preset_config = PRESET_CONFIGS[DEFAULT_PRESET]
|
| 288 |
+
if "DEFAULT_DURATION_SEC" not in os.environ:
|
| 289 |
+
DEFAULT_DURATION_SEC = _default_preset_config["duration"]
|
| 290 |
+
if "DEFAULT_QUANT_MODE" not in os.environ:
|
| 291 |
+
DEFAULT_QUANT_MODE = _default_preset_config["quant_mode"]
|
| 292 |
+
if "DEFAULT_OFFLOAD_MODE" not in os.environ:
|
| 293 |
+
DEFAULT_OFFLOAD_MODE = _default_preset_config["offload_mode"]
|
| 294 |
+
if "DEFAULT_GENERATION_MODE" not in os.environ:
|
| 295 |
+
DEFAULT_GENERATION_MODE = _default_preset_config["generation_mode"]
|
| 296 |
+
if "DEFAULT_SPEED_SUBMODE" not in os.environ:
|
| 297 |
+
DEFAULT_SPEED_SUBMODE = _default_preset_config["speed_submode"]
|
| 298 |
|
| 299 |
|
| 300 |
class ModelManager:
|
| 301 |
def __init__(self, model_path: str, use_deepspeed_override: Optional[bool] = None):
|
| 302 |
import torch
|
| 303 |
+
from heartlib import HeartMuLaGenPipeline
|
| 304 |
|
| 305 |
self.model_path = model_path
|
| 306 |
self.device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
|
| 307 |
self.dtype = torch.bfloat16 if torch.cuda.is_available() else torch.float32
|
| 308 |
self._gen_pipes: Dict[Tuple[str, str, str], "HeartMuLaGenPipeline"] = {}
|
|
|
|
| 309 |
if use_deepspeed_override is None:
|
| 310 |
self.use_deepspeed = os.getenv("USE_DEEPSPEED_INFERENCE", "0").lower() in ("1", "true", "yes")
|
| 311 |
else:
|
| 312 |
self.use_deepspeed = use_deepspeed_override
|
| 313 |
self.ds_inference_config = self._make_ds_inference_config()
|
| 314 |
self._HeartMuLaGenPipeline = HeartMuLaGenPipeline
|
|
|
|
| 315 |
|
| 316 |
def _make_ds_inference_config(self) -> Dict[str, Any]:
|
| 317 |
if not self.use_deepspeed:
|
|
|
|
| 332 |
return None
|
| 333 |
if self.device.type != "cuda":
|
| 334 |
raise gr.Error("Quantization requires CUDA.")
|
| 335 |
+
if quant_mode == "fp8":
|
| 336 |
+
return None
|
| 337 |
if quant_mode == "4bit":
|
| 338 |
quant_type = "nf4"
|
| 339 |
try:
|
|
|
|
| 363 |
version=version,
|
| 364 |
codec_version=codec_version,
|
| 365 |
bnb_config=bnb_config,
|
| 366 |
+
torchao_quantize=quant_mode == "fp8",
|
| 367 |
lazy_load=True,
|
| 368 |
use_deepspeed=self.use_deepspeed,
|
| 369 |
ds_inference_config=self.ds_inference_config,
|
| 370 |
)
|
| 371 |
return self._gen_pipes[key]
|
| 372 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 373 |
|
| 374 |
model_managers: Dict[str, ModelManager] = {}
|
| 375 |
|
|
|
|
| 535 |
print("")
|
| 536 |
|
| 537 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 538 |
def load_pipeline(model_path, version, codec_version, quant_mode, use_acceleration: bool):
|
| 539 |
"""Load HeartMuLa pipeline (lazy)"""
|
|
|
|
|
|
|
| 540 |
manager = get_model_manager(use_acceleration)
|
| 541 |
print(f"Using model from {model_path} on {manager.device}...")
|
| 542 |
return manager.get_gen_pipeline(version, codec_version, quant_mode)
|
| 543 |
|
| 544 |
|
| 545 |
+
def _validate_generation_inputs(lyrics: str, tags: str) -> None:
|
| 546 |
+
if not lyrics.strip():
|
| 547 |
+
raise gr.Error("Please enter lyrics")
|
| 548 |
+
if not tags.strip():
|
| 549 |
+
raise gr.Error("Please enter tags")
|
| 550 |
|
| 551 |
|
| 552 |
def generate(
|
|
|
|
| 567 |
):
|
| 568 |
"""Generate music"""
|
| 569 |
import torch
|
| 570 |
+
_validate_generation_inputs(lyrics, tags)
|
|
|
|
|
|
|
|
|
|
|
|
|
| 571 |
|
| 572 |
max_audio_length_ms = int(duration_sec * 1000)
|
| 573 |
|
|
|
|
| 578 |
pipe = load_pipeline(MODEL_PATH, version, codec_version, quant_mode, use_acceleration)
|
| 579 |
output_path = os.path.join(DATA_DIR, f"gen_{uuid.uuid4().hex}.wav")
|
| 580 |
|
| 581 |
+
with torch.inference_mode():
|
| 582 |
pipe(
|
| 583 |
{
|
| 584 |
"lyrics": lyrics,
|
|
|
|
| 606 |
|
| 607 |
except Exception as e:
|
| 608 |
raise gr.Error(f"Generation error: {str(e)}")
|
| 609 |
+
finally:
|
| 610 |
+
if not keep_model_loaded and torch.cuda.is_available():
|
| 611 |
+
torch.cuda.empty_cache()
|
| 612 |
|
| 613 |
|
| 614 |
@_gpu_guard
|
|
|
|
| 679 |
)
|
| 680 |
|
| 681 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 682 |
def _normalize_stream_chunk(chunk: np.ndarray) -> np.ndarray:
|
| 683 |
chunk = np.nan_to_num(chunk, nan=0.0, posinf=0.0, neginf=0.0)
|
| 684 |
return np.clip(chunk, -1.0, 1.0)
|
|
|
|
| 697 |
keep_model_loaded,
|
| 698 |
offload_mode,
|
| 699 |
backend,
|
|
|
|
| 700 |
use_acceleration,
|
| 701 |
) -> Iterator[Tuple[int, np.ndarray]]:
|
| 702 |
+
_validate_generation_inputs(lyrics, tags)
|
| 703 |
if backend == "exllama_v2":
|
| 704 |
raise gr.Error("ExLlamaV2 backend is not implemented yet.")
|
| 705 |
pipe = load_pipeline(MODEL_PATH, version, codec_version, quant_mode, use_acceleration)
|
|
|
|
| 710 |
temperature=temperature,
|
| 711 |
topk=topk,
|
| 712 |
cfg_scale=cfg_scale,
|
| 713 |
+
chunk_frames=BLOCK_FRAMES,
|
| 714 |
keep_model_loaded=keep_model_loaded,
|
| 715 |
offload_mode=offload_mode,
|
| 716 |
):
|
|
|
|
| 718 |
chunk = chunk.squeeze(0)
|
| 719 |
chunk_np = chunk.cpu().numpy()
|
| 720 |
chunk_np = _normalize_stream_chunk(chunk_np)
|
| 721 |
+
print(f"stream chunk: samples={chunk_np.shape[0]} sr={AUDIO_SAMPLE_RATE}")
|
| 722 |
+
yield AUDIO_SAMPLE_RATE, chunk_np
|
| 723 |
|
| 724 |
|
| 725 |
def stream_generate(
|
|
|
|
| 735 |
keep_model_loaded,
|
| 736 |
offload_mode,
|
| 737 |
backend,
|
|
|
|
|
|
|
| 738 |
use_acceleration,
|
| 739 |
):
|
| 740 |
try:
|
| 741 |
+
_validate_generation_inputs(lyrics, tags)
|
| 742 |
+
start_threshold = max(MIN_BUFFER_BLOCKS, PREFETCH_BLOCKS)
|
| 743 |
+
queue = []
|
| 744 |
+
started = False
|
| 745 |
+
print("block stream start:", f"block_sec={MODEL_DETOKENIZE_INTERVAL_SEC}", f"duration_sec={duration_sec}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 746 |
for sr, chunk_np in generate_music_streaming(
|
| 747 |
lyrics=lyrics,
|
| 748 |
tags=tags,
|
|
|
|
| 756 |
keep_model_loaded=keep_model_loaded,
|
| 757 |
offload_mode=offload_mode,
|
| 758 |
backend=backend,
|
|
|
|
| 759 |
use_acceleration=use_acceleration,
|
| 760 |
):
|
| 761 |
chunk_np = chunk_np.astype("float32", copy=False)
|
| 762 |
+
queue.append(chunk_np)
|
| 763 |
+
if not started and len(queue) < start_threshold and len(queue) < MAX_QUEUE_BLOCKS:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 764 |
continue
|
| 765 |
+
if not started:
|
| 766 |
+
started = True
|
| 767 |
+
print(f"block stream start playback: buffered_blocks={len(queue)}")
|
| 768 |
+
while queue:
|
| 769 |
+
block = queue.pop(0)
|
| 770 |
+
print(f"block stream yield: samples={block.shape[0]}")
|
| 771 |
+
yield sr, block
|
|
|
|
| 772 |
except Exception as e:
|
| 773 |
raise gr.Error(f"Streaming error: {str(e)}")
|
| 774 |
|
|
|
|
| 787 |
keep_model_loaded,
|
| 788 |
offload_mode,
|
| 789 |
backend,
|
|
|
|
|
|
|
| 790 |
):
|
| 791 |
return stream_generate(
|
| 792 |
lyrics,
|
|
|
|
| 801 |
keep_model_loaded,
|
| 802 |
offload_mode,
|
| 803 |
backend,
|
|
|
|
|
|
|
| 804 |
True,
|
| 805 |
)
|
| 806 |
|
|
|
|
| 970 |
|
| 971 |
def create_ui():
|
| 972 |
"""Create Gradio UI"""
|
| 973 |
+
show_stream_default = (
|
| 974 |
+
DEFAULT_GENERATION_MODE != "Original (No Acceleration)"
|
| 975 |
+
and DEFAULT_SPEED_SUBMODE == "Streaming"
|
| 976 |
+
)
|
| 977 |
|
| 978 |
with gr.Blocks(title="HeartMuLa Music Generation") as demo:
|
| 979 |
gr.Markdown("# HeartMuLa Music Generation")
|
| 980 |
gr.Markdown("Generate music from lyrics and style tags")
|
| 981 |
+
gr.Markdown("Tip: start with the **ZeroGPU Safe** preset for reliable generation on small GPUs.")
|
| 982 |
|
| 983 |
with gr.Tabs():
|
| 984 |
# Tab 1: Music Generation
|
|
|
|
| 1028 |
t7 = gr.CheckboxGroup(choices=TAG_DATA["Topic"], label="Topic")
|
| 1029 |
tag_checkboxes.append(t7)
|
| 1030 |
|
| 1031 |
+
gr.Markdown("### Quick Presets")
|
| 1032 |
+
with gr.Row():
|
| 1033 |
+
preset_selector = gr.Dropdown(
|
| 1034 |
+
choices=list(PRESET_CONFIGS.keys()),
|
| 1035 |
+
value=DEFAULT_PRESET,
|
| 1036 |
+
label="Preset"
|
| 1037 |
+
)
|
| 1038 |
+
apply_preset_btn = gr.Button("Apply Preset", size="sm")
|
| 1039 |
+
|
| 1040 |
# Generation parameters
|
| 1041 |
with gr.Row():
|
| 1042 |
cfg_scale = gr.Slider(0.0, 3.0, value=1.5, step=0.1, label="CFG Scale")
|
| 1043 |
+
duration = gr.Slider(10, 300, value=DEFAULT_DURATION_SEC, step=10, label="Duration (sec)")
|
| 1044 |
|
| 1045 |
with gr.Row():
|
| 1046 |
temperature = gr.Slider(0.1, 2.0, value=1.0, step=0.1, label="Temperature")
|
|
|
|
| 1063 |
label="Codec Version"
|
| 1064 |
)
|
| 1065 |
quant_mode = gr.Dropdown(
|
| 1066 |
+
choices=[
|
| 1067 |
+
("None", "none"),
|
| 1068 |
+
("4-bit (NF4/FP4)", "4bit"),
|
| 1069 |
+
("8-bit", "8bit"),
|
| 1070 |
+
("FP8 (TorchAO)", "fp8"),
|
| 1071 |
+
],
|
| 1072 |
+
value=DEFAULT_QUANT_MODE,
|
| 1073 |
+
label="Quantization (ZeroGPU recommended: 4-bit)"
|
| 1074 |
)
|
| 1075 |
keep_model_loaded = gr.Checkbox(
|
| 1076 |
value=DEFAULT_KEEP_MODEL_LOADED,
|
|
|
|
| 1078 |
)
|
| 1079 |
offload_mode = gr.Dropdown(
|
| 1080 |
choices=["auto", "aggressive"],
|
| 1081 |
+
value=DEFAULT_OFFLOAD_MODE,
|
| 1082 |
label="Offload Mode"
|
| 1083 |
)
|
| 1084 |
output_format = gr.Radio(
|
| 1085 |
choices=[("WAV", "wav"), ("MP3", "mp3")],
|
| 1086 |
value="wav",
|
| 1087 |
+
label="Output Format",
|
| 1088 |
+
visible=not show_stream_default,
|
| 1089 |
)
|
| 1090 |
+
gr.Markdown(
|
| 1091 |
+
f"Streaming is block-based: {MODEL_DETOKENIZE_INTERVAL_SEC:.2f}s per block "
|
| 1092 |
+
f"({BLOCK_FRAMES} frames, {BLOCK_SAMPLES} samples)."
|
| 1093 |
)
|
| 1094 |
|
| 1095 |
gr.Markdown("### 🚀 Generation")
|
| 1096 |
|
| 1097 |
generation_mode = gr.Radio(
|
| 1098 |
choices=["Original (No Acceleration)", "Accelerated"],
|
| 1099 |
+
value=DEFAULT_GENERATION_MODE,
|
| 1100 |
label="Generation Mode",
|
| 1101 |
)
|
| 1102 |
|
| 1103 |
speed_submode = gr.Radio(
|
| 1104 |
+
choices=[
|
| 1105 |
+
("Standard", "Standard"),
|
| 1106 |
+
("Block Streaming (Preview)", "Streaming"),
|
| 1107 |
+
],
|
| 1108 |
+
value=DEFAULT_SPEED_SUBMODE,
|
| 1109 |
label="Accelerated Options",
|
| 1110 |
+
visible=DEFAULT_GENERATION_MODE != "Original (No Acceleration)",
|
| 1111 |
)
|
| 1112 |
|
| 1113 |
+
btn_original = gr.Button(
|
| 1114 |
+
"🎼 Generate Music (Original)",
|
| 1115 |
+
variant="primary",
|
| 1116 |
+
size="lg",
|
| 1117 |
+
visible=DEFAULT_GENERATION_MODE == "Original (No Acceleration)",
|
| 1118 |
+
)
|
| 1119 |
+
btn_accel = gr.Button(
|
| 1120 |
+
"🎼 Generate Music (Accelerated)",
|
| 1121 |
+
variant="primary",
|
| 1122 |
+
size="lg",
|
| 1123 |
+
visible=DEFAULT_GENERATION_MODE != "Original (No Acceleration)"
|
| 1124 |
+
and not show_stream_default,
|
| 1125 |
+
)
|
| 1126 |
+
btn_stream = gr.Button(
|
| 1127 |
+
"🎼 Generate Music (Block Streaming)",
|
| 1128 |
+
variant="primary",
|
| 1129 |
+
size="lg",
|
| 1130 |
+
visible=show_stream_default,
|
| 1131 |
+
)
|
| 1132 |
+
cancel_stream_btn = gr.Button(
|
| 1133 |
+
"Cancel Streaming",
|
| 1134 |
+
variant="secondary",
|
| 1135 |
+
size="lg",
|
| 1136 |
+
visible=show_stream_default,
|
| 1137 |
+
)
|
| 1138 |
cancel_state = gr.State()
|
| 1139 |
|
| 1140 |
with gr.Column():
|
| 1141 |
# Notice section
|
| 1142 |
with gr.Accordion("Usage Notice", open=True):
|
| 1143 |
gr.Markdown("""
|
| 1144 |
+
### ZeroGPU Tips
|
| 1145 |
+
- Prefer **4-bit quantization** and **aggressive offload**
|
| 1146 |
+
- Keep durations short (<= 60s) for faster first results
|
| 1147 |
+
- **Streaming is block-based**, not token streaming: each block is ~29.76s
|
| 1148 |
+
- Use Streaming for preview; Standard mode outputs a full downloadable file
|
| 1149 |
+
- Try **FP8 + AOTI** (preset: ZeroGPU AOTI FP8) for the fastest H200 path
|
| 1150 |
+
|
| 1151 |
### Lyrics Format Requirements
|
| 1152 |
|
| 1153 |
**Automatic Processing:**
|
|
|
|
| 1187 |
label="Generated Music (Full)",
|
| 1188 |
type="filepath",
|
| 1189 |
interactive=False,
|
| 1190 |
+
visible=not show_stream_default,
|
| 1191 |
)
|
| 1192 |
stream_audio = gr.Audio(
|
| 1193 |
+
label="Block Streaming Audio (Preview)",
|
| 1194 |
streaming=True,
|
| 1195 |
autoplay=True,
|
| 1196 |
type="numpy",
|
| 1197 |
format="wav",
|
| 1198 |
interactive=False,
|
| 1199 |
+
visible=show_stream_default,
|
| 1200 |
)
|
| 1201 |
|
| 1202 |
# Event handlers for tag selection
|
|
|
|
| 1218 |
gr.update(visible=False), # btn_accel
|
| 1219 |
gr.update(visible=False), # btn_stream
|
| 1220 |
gr.update(visible=False), # cancel_stream_btn
|
| 1221 |
+
gr.update(visible=True), # output_format
|
| 1222 |
+
gr.update(visible=True), # output_audio_file
|
| 1223 |
+
gr.update(visible=False), # stream_audio
|
| 1224 |
)
|
| 1225 |
show_stream = spd_mode == "Streaming"
|
| 1226 |
return (
|
|
|
|
| 1229 |
gr.update(visible=not show_stream), # btn_accel
|
| 1230 |
gr.update(visible=show_stream), # btn_stream
|
| 1231 |
gr.update(visible=show_stream), # cancel_stream_btn
|
| 1232 |
+
gr.update(visible=not show_stream), # output_format
|
| 1233 |
+
gr.update(visible=not show_stream), # output_audio_file
|
| 1234 |
+
gr.update(visible=show_stream), # stream_audio
|
| 1235 |
+
)
|
| 1236 |
+
|
| 1237 |
+
def apply_preset(preset_name):
|
| 1238 |
+
preset = PRESET_CONFIGS.get(preset_name, PRESET_CONFIGS[DEFAULT_PRESET])
|
| 1239 |
+
return (
|
| 1240 |
+
gr.update(value=preset["cfg_scale"]),
|
| 1241 |
+
gr.update(value=preset["duration"]),
|
| 1242 |
+
gr.update(value=preset["temperature"]),
|
| 1243 |
+
gr.update(value=preset["topk"]),
|
| 1244 |
+
gr.update(value=preset["quant_mode"]),
|
| 1245 |
+
gr.update(value=preset["keep_model_loaded"]),
|
| 1246 |
+
gr.update(value=preset["offload_mode"]),
|
| 1247 |
+
gr.update(value=preset["generation_mode"]),
|
| 1248 |
+
gr.update(value=preset["speed_submode"]),
|
| 1249 |
)
|
| 1250 |
|
| 1251 |
generation_mode.change(
|
| 1252 |
fn=update_visibility,
|
| 1253 |
inputs=[generation_mode, speed_submode],
|
| 1254 |
+
outputs=[
|
| 1255 |
+
speed_submode,
|
| 1256 |
+
btn_original,
|
| 1257 |
+
btn_accel,
|
| 1258 |
+
btn_stream,
|
| 1259 |
+
cancel_stream_btn,
|
| 1260 |
+
output_format,
|
| 1261 |
+
output_audio_file,
|
| 1262 |
+
stream_audio,
|
| 1263 |
+
],
|
| 1264 |
)
|
| 1265 |
speed_submode.change(
|
| 1266 |
fn=update_visibility,
|
| 1267 |
inputs=[generation_mode, speed_submode],
|
| 1268 |
+
outputs=[
|
| 1269 |
+
speed_submode,
|
| 1270 |
+
btn_original,
|
| 1271 |
+
btn_accel,
|
| 1272 |
+
btn_stream,
|
| 1273 |
+
cancel_stream_btn,
|
| 1274 |
+
output_format,
|
| 1275 |
+
output_audio_file,
|
| 1276 |
+
stream_audio,
|
| 1277 |
+
],
|
| 1278 |
+
)
|
| 1279 |
+
|
| 1280 |
+
preset_event = apply_preset_btn.click(
|
| 1281 |
+
fn=apply_preset,
|
| 1282 |
+
inputs=[preset_selector],
|
| 1283 |
+
outputs=[
|
| 1284 |
+
cfg_scale,
|
| 1285 |
+
duration,
|
| 1286 |
+
temperature,
|
| 1287 |
+
topk,
|
| 1288 |
+
quant_mode,
|
| 1289 |
+
keep_model_loaded,
|
| 1290 |
+
offload_mode,
|
| 1291 |
+
generation_mode,
|
| 1292 |
+
speed_submode,
|
| 1293 |
+
],
|
| 1294 |
+
)
|
| 1295 |
+
|
| 1296 |
+
preset_event.then(
|
| 1297 |
+
fn=update_visibility,
|
| 1298 |
+
inputs=[generation_mode, speed_submode],
|
| 1299 |
+
outputs=[
|
| 1300 |
+
speed_submode,
|
| 1301 |
+
btn_original,
|
| 1302 |
+
btn_accel,
|
| 1303 |
+
btn_stream,
|
| 1304 |
+
cancel_stream_btn,
|
| 1305 |
+
output_format,
|
| 1306 |
+
output_audio_file,
|
| 1307 |
+
stream_audio,
|
| 1308 |
+
],
|
| 1309 |
)
|
| 1310 |
|
| 1311 |
btn_original.click(
|
|
|
|
| 1367 |
keep_model_loaded,
|
| 1368 |
offload_mode,
|
| 1369 |
backend,
|
|
|
|
|
|
|
| 1370 |
],
|
| 1371 |
outputs=[stream_audio],
|
| 1372 |
concurrency_id="gpu_queue",
|
|
|
|
| 1511 |
outputs=[lyrics]
|
| 1512 |
)
|
| 1513 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1514 |
return demo
|
| 1515 |
|
|
|
|
|
|
|
| 1516 |
demo.queue(max_size=GRADIO_QUEUE_MAX_SIZE, default_concurrency_limit=GRADIO_DEFAULT_CONCURRENCY)
|
| 1517 |
|
| 1518 |
if __name__ == "__main__":
|
src/heartlib/pipelines/music_generation.py
CHANGED
|
@@ -1,17 +1,30 @@
|
|
| 1 |
import gc
|
|
|
|
| 2 |
import json
|
| 3 |
import os
|
| 4 |
from dataclasses import dataclass
|
| 5 |
-
from typing import Any, Dict, Optional, Iterator
|
| 6 |
|
| 7 |
import torch
|
| 8 |
import torchaudio
|
| 9 |
from tokenizers import Tokenizer
|
| 10 |
from transformers import BitsAndBytesConfig
|
| 11 |
|
| 12 |
-
from ..heartmula.modeling_heartmula import HeartMuLa
|
| 13 |
from ..heartcodec.modeling_heartcodec import HeartCodec
|
| 14 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 15 |
|
| 16 |
@dataclass
|
| 17 |
class HeartMuLaGenConfig:
|
|
@@ -44,6 +57,9 @@ class HeartMuLaGenPipeline:
|
|
| 44 |
lazy_load: bool = False,
|
| 45 |
use_deepspeed: bool = False,
|
| 46 |
ds_inference_config: Optional[Dict[str, Any]] = None,
|
|
|
|
|
|
|
|
|
|
| 47 |
):
|
| 48 |
self.model = model
|
| 49 |
self.audio_codec = audio_codec
|
|
@@ -62,6 +78,17 @@ class HeartMuLaGenPipeline:
|
|
| 62 |
self._ds_inference_applied = False
|
| 63 |
self._parallel_number = num_quantizers + 1 if num_quantizers else 9
|
| 64 |
self._muq_dim = model.config.muq_dim if model else None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 65 |
|
| 66 |
def _init_deepspeed_inference(self):
|
| 67 |
if not self.use_deepspeed or self._ds_inference_applied:
|
|
@@ -92,6 +119,9 @@ class HeartMuLaGenPipeline:
|
|
| 92 |
)
|
| 93 |
if str(next(self.model.parameters()).device) != str(self.device):
|
| 94 |
self.model.to(self.device)
|
|
|
|
|
|
|
|
|
|
| 95 |
self.model.eval()
|
| 96 |
self._init_deepspeed_inference()
|
| 97 |
self._muq_dim = self.model.config.muq_dim
|
|
@@ -120,6 +150,157 @@ class HeartMuLaGenPipeline:
|
|
| 120 |
return torch.autocast(device_type="cuda", dtype=self.dtype)
|
| 121 |
return torch.inference_mode()
|
| 122 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 123 |
def preprocess(self, inputs: Dict[str, Any], cfg_scale: float):
|
| 124 |
self.load_heartmula()
|
| 125 |
tags = inputs["tags"]
|
|
@@ -258,6 +439,7 @@ class HeartMuLaGenPipeline:
|
|
| 258 |
offload_mode: str = "auto",
|
| 259 |
) -> Iterator[torch.Tensor]:
|
| 260 |
model_inputs = self.preprocess(inputs, cfg_scale=cfg_scale)
|
|
|
|
| 261 |
self.load_heartcodec()
|
| 262 |
|
| 263 |
prompt_tokens = model_inputs["tokens"]
|
|
@@ -352,6 +534,7 @@ class HeartMuLaGenPipeline:
|
|
| 352 |
offload_mode: str = "auto",
|
| 353 |
):
|
| 354 |
model_inputs = self.preprocess(inputs, cfg_scale=cfg_scale)
|
|
|
|
| 355 |
frames = self._generate_frames(model_inputs, max_audio_length_ms, temperature, topk, cfg_scale)
|
| 356 |
wav = self._decode_frames(frames)
|
| 357 |
torchaudio.save(save_path, wav, 48000)
|
|
@@ -369,6 +552,9 @@ class HeartMuLaGenPipeline:
|
|
| 369 |
lazy_load: bool = False,
|
| 370 |
use_deepspeed: bool = False,
|
| 371 |
ds_inference_config: Optional[Dict[str, Any]] = None,
|
|
|
|
|
|
|
|
|
|
| 372 |
):
|
| 373 |
if os.path.isfile(vocab_path := os.path.join(pretrained_path, "tokenizer.json")):
|
| 374 |
tokenizer = Tokenizer.from_file(vocab_path)
|
|
@@ -415,4 +601,7 @@ class HeartMuLaGenPipeline:
|
|
| 415 |
lazy_load=lazy_load,
|
| 416 |
use_deepspeed=use_deepspeed,
|
| 417 |
ds_inference_config=ds_inference_config,
|
|
|
|
|
|
|
|
|
|
| 418 |
)
|
|
|
|
| 1 |
import gc
|
| 2 |
+
import inspect
|
| 3 |
import json
|
| 4 |
import os
|
| 5 |
from dataclasses import dataclass
|
| 6 |
+
from typing import Any, Dict, Optional, Iterator, Tuple
|
| 7 |
|
| 8 |
import torch
|
| 9 |
import torchaudio
|
| 10 |
from tokenizers import Tokenizer
|
| 11 |
from transformers import BitsAndBytesConfig
|
| 12 |
|
| 13 |
+
from ..heartmula.modeling_heartmula import HeartMuLa, _index_causal_mask
|
| 14 |
from ..heartcodec.modeling_heartcodec import HeartCodec
|
| 15 |
|
| 16 |
+
try:
|
| 17 |
+
import spaces # type: ignore
|
| 18 |
+
except Exception: # pragma: no cover - optional dependency
|
| 19 |
+
spaces = None
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
def _env_bool(name: str) -> Optional[bool]:
|
| 23 |
+
value = os.environ.get(name)
|
| 24 |
+
if value is None:
|
| 25 |
+
return None
|
| 26 |
+
return value.strip().lower() in ("1", "true", "yes", "y", "on")
|
| 27 |
+
|
| 28 |
|
| 29 |
@dataclass
|
| 30 |
class HeartMuLaGenConfig:
|
|
|
|
| 57 |
lazy_load: bool = False,
|
| 58 |
use_deepspeed: bool = False,
|
| 59 |
ds_inference_config: Optional[Dict[str, Any]] = None,
|
| 60 |
+
torchao_quantize: bool = False,
|
| 61 |
+
aoti_enabled: Optional[bool] = None,
|
| 62 |
+
aoti_dynamic_shapes: Optional[bool] = None,
|
| 63 |
):
|
| 64 |
self.model = model
|
| 65 |
self.audio_codec = audio_codec
|
|
|
|
| 78 |
self._ds_inference_applied = False
|
| 79 |
self._parallel_number = num_quantizers + 1 if num_quantizers else 9
|
| 80 |
self._muq_dim = model.config.muq_dim if model else None
|
| 81 |
+
self._torchao_quantize = torchao_quantize
|
| 82 |
+
space_default = spaces is not None and os.environ.get("SPACE_ID") is not None
|
| 83 |
+
env_aoti = _env_bool("ENABLE_AOTI")
|
| 84 |
+
env_dynamic = _env_bool("AOTI_DYNAMIC_SHAPES")
|
| 85 |
+
self._aoti_enabled = env_aoti if aoti_enabled is None else aoti_enabled
|
| 86 |
+
if self._aoti_enabled is None:
|
| 87 |
+
self._aoti_enabled = space_default
|
| 88 |
+
self._aoti_dynamic_shapes = env_dynamic if aoti_dynamic_shapes is None else aoti_dynamic_shapes
|
| 89 |
+
if self._aoti_dynamic_shapes is None:
|
| 90 |
+
self._aoti_dynamic_shapes = space_default
|
| 91 |
+
self._aoti_compiled = False
|
| 92 |
|
| 93 |
def _init_deepspeed_inference(self):
|
| 94 |
if not self.use_deepspeed or self._ds_inference_applied:
|
|
|
|
| 119 |
)
|
| 120 |
if str(next(self.model.parameters()).device) != str(self.device):
|
| 121 |
self.model.to(self.device)
|
| 122 |
+
if self._torchao_quantize and not self._aoti_enabled:
|
| 123 |
+
self._maybe_fp8_quantize(self.model.backbone)
|
| 124 |
+
self._maybe_fp8_quantize(self.model.decoder)
|
| 125 |
self.model.eval()
|
| 126 |
self._init_deepspeed_inference()
|
| 127 |
self._muq_dim = self.model.config.muq_dim
|
|
|
|
| 150 |
return torch.autocast(device_type="cuda", dtype=self.dtype)
|
| 151 |
return torch.inference_mode()
|
| 152 |
|
| 153 |
+
def _supports_aoti(self) -> Tuple[bool, str]:
|
| 154 |
+
if not self._aoti_enabled:
|
| 155 |
+
return False, "AOTI disabled"
|
| 156 |
+
if spaces is None:
|
| 157 |
+
return False, "spaces package not available"
|
| 158 |
+
if not torch.cuda.is_available():
|
| 159 |
+
return False, "CUDA unavailable"
|
| 160 |
+
if not hasattr(torch, "export"):
|
| 161 |
+
return False, "torch.export unavailable"
|
| 162 |
+
if self.use_deepspeed:
|
| 163 |
+
return False, "DeepSpeed enabled"
|
| 164 |
+
if self.bnb_config is not None:
|
| 165 |
+
return False, "bitsandbytes quantization active"
|
| 166 |
+
return True, ""
|
| 167 |
+
|
| 168 |
+
def _maybe_fp8_quantize(self, module: torch.nn.Module) -> bool:
|
| 169 |
+
if not self._torchao_quantize:
|
| 170 |
+
return False
|
| 171 |
+
if not torch.cuda.is_available():
|
| 172 |
+
print("AOTI FP8: CUDA unavailable, skipping torchao quantization.")
|
| 173 |
+
return False
|
| 174 |
+
try:
|
| 175 |
+
major, _ = torch.cuda.get_device_capability()
|
| 176 |
+
except Exception:
|
| 177 |
+
major = 0
|
| 178 |
+
if major < 9:
|
| 179 |
+
print("AOTI FP8: requires CUDA compute capability >= 9.0, skipping.")
|
| 180 |
+
return False
|
| 181 |
+
try:
|
| 182 |
+
from torchao.quantization import quantize_, Float8DynamicActivationFloat8WeightConfig
|
| 183 |
+
except Exception:
|
| 184 |
+
print("AOTI FP8: torchao not available, skipping.")
|
| 185 |
+
return False
|
| 186 |
+
quantize_(module, Float8DynamicActivationFloat8WeightConfig())
|
| 187 |
+
return True
|
| 188 |
+
|
| 189 |
+
def _aoti_compile_module(
|
| 190 |
+
self,
|
| 191 |
+
module: torch.nn.Module,
|
| 192 |
+
example_kwargs: Dict[str, Any],
|
| 193 |
+
dynamic_shapes: Optional[Dict[str, Any]] = None,
|
| 194 |
+
) -> None:
|
| 195 |
+
if spaces is None:
|
| 196 |
+
return
|
| 197 |
+
with spaces.aoti_capture(module) as call:
|
| 198 |
+
module(**example_kwargs)
|
| 199 |
+
exported = torch.export.export(
|
| 200 |
+
module,
|
| 201 |
+
args=call.args,
|
| 202 |
+
kwargs=call.kwargs,
|
| 203 |
+
dynamic_shapes=dynamic_shapes,
|
| 204 |
+
)
|
| 205 |
+
compiled = spaces.aoti_compile(exported)
|
| 206 |
+
spaces.aoti_apply(compiled, module)
|
| 207 |
+
|
| 208 |
+
def _maybe_apply_aoti(self, model_inputs: Dict[str, Any], cfg_scale: float) -> None:
|
| 209 |
+
if self._aoti_compiled:
|
| 210 |
+
return
|
| 211 |
+
ok, reason = self._supports_aoti()
|
| 212 |
+
if not ok:
|
| 213 |
+
print(f"AOTI: skip ({reason}).")
|
| 214 |
+
self._aoti_compiled = True
|
| 215 |
+
return
|
| 216 |
+
|
| 217 |
+
try:
|
| 218 |
+
self.load_heartmula()
|
| 219 |
+
|
| 220 |
+
prompt_tokens = model_inputs["tokens"]
|
| 221 |
+
prompt_tokens_mask = model_inputs["tokens_mask"]
|
| 222 |
+
continuous_segment = model_inputs["muq_embed"]
|
| 223 |
+
starts = model_inputs["muq_idx"]
|
| 224 |
+
prompt_pos = model_inputs["pos"]
|
| 225 |
+
|
| 226 |
+
bs_size = 2 if cfg_scale != 1.0 else 1
|
| 227 |
+
self.model.setup_caches(bs_size)
|
| 228 |
+
|
| 229 |
+
uncond_mask = None
|
| 230 |
+
if cfg_scale > 1.0 and prompt_tokens.shape[0] > 1:
|
| 231 |
+
actual_B = prompt_tokens.shape[0] // 2
|
| 232 |
+
uncond_mask = torch.cat(
|
| 233 |
+
[
|
| 234 |
+
torch.zeros(actual_B, dtype=torch.bool, device=prompt_tokens.device),
|
| 235 |
+
torch.ones(actual_B, dtype=torch.bool, device=prompt_tokens.device),
|
| 236 |
+
]
|
| 237 |
+
)
|
| 238 |
+
|
| 239 |
+
embeds = self.model._embed_tokens(prompt_tokens, uncond_mask=uncond_mask)
|
| 240 |
+
masked_embeds = embeds * prompt_tokens_mask.unsqueeze(-1)
|
| 241 |
+
h = masked_embeds.sum(dim=2, dtype=embeds.dtype)
|
| 242 |
+
if continuous_segment is not None:
|
| 243 |
+
continuous_segment = self.model.muq_linear(continuous_segment)
|
| 244 |
+
if uncond_mask is not None:
|
| 245 |
+
uncond_embed = self.model.unconditional_text_embedding(
|
| 246 |
+
torch.zeros(1, device=prompt_tokens.device, dtype=torch.long)
|
| 247 |
+
)
|
| 248 |
+
mask_expanded = uncond_mask.view(h.shape[0], 1).expand_as(continuous_segment)
|
| 249 |
+
continuous_segment = torch.where(mask_expanded, uncond_embed, continuous_segment)
|
| 250 |
+
batch_indices = torch.arange(h.shape[0], device=h.device)
|
| 251 |
+
h[batch_indices, starts] = continuous_segment
|
| 252 |
+
|
| 253 |
+
curr_backbone_mask = _index_causal_mask(self.model.backbone_causal_mask, prompt_pos)
|
| 254 |
+
|
| 255 |
+
backbone_params = list(inspect.signature(self.model.backbone.forward).parameters.keys())
|
| 256 |
+
backbone_name = backbone_params[1] if len(backbone_params) > 1 else "x"
|
| 257 |
+
backbone_kwargs = {
|
| 258 |
+
backbone_name: h,
|
| 259 |
+
"input_pos": prompt_pos,
|
| 260 |
+
"mask": curr_backbone_mask,
|
| 261 |
+
}
|
| 262 |
+
|
| 263 |
+
if self._aoti_dynamic_shapes and hasattr(torch, "export"):
|
| 264 |
+
seq_dim = torch.export.Dim("seq", min=4, max=self.model.backbone.max_seq_len)
|
| 265 |
+
backbone_dynamic = {
|
| 266 |
+
backbone_name: {1: seq_dim},
|
| 267 |
+
"input_pos": {1: seq_dim},
|
| 268 |
+
"mask": {1: seq_dim},
|
| 269 |
+
}
|
| 270 |
+
else:
|
| 271 |
+
backbone_dynamic = None
|
| 272 |
+
|
| 273 |
+
self._maybe_fp8_quantize(self.model.backbone)
|
| 274 |
+
self._aoti_compile_module(self.model.backbone, backbone_kwargs, backbone_dynamic)
|
| 275 |
+
|
| 276 |
+
last_h = h[:, -1, :]
|
| 277 |
+
c0_sample = torch.zeros((h.shape[0], 1), device=h.device, dtype=torch.long)
|
| 278 |
+
c0_embed = self.model._embed_audio(0, c0_sample)
|
| 279 |
+
curr_h = torch.cat([last_h.unsqueeze(1), c0_embed], dim=1)
|
| 280 |
+
curr_pos = (
|
| 281 |
+
torch.arange(0, curr_h.size(1), device=curr_h.device)
|
| 282 |
+
.unsqueeze(0)
|
| 283 |
+
.repeat(curr_h.size(0), 1)
|
| 284 |
+
)
|
| 285 |
+
curr_decoder_mask = _index_causal_mask(self.model.decoder_causal_mask, curr_pos)
|
| 286 |
+
|
| 287 |
+
decoder_params = list(inspect.signature(self.model.decoder.forward).parameters.keys())
|
| 288 |
+
decoder_name = decoder_params[1] if len(decoder_params) > 1 else "x"
|
| 289 |
+
decoder_kwargs = {
|
| 290 |
+
decoder_name: self.model.projection(curr_h),
|
| 291 |
+
"input_pos": curr_pos,
|
| 292 |
+
"mask": curr_decoder_mask,
|
| 293 |
+
}
|
| 294 |
+
decoder_dynamic = None
|
| 295 |
+
|
| 296 |
+
self._maybe_fp8_quantize(self.model.decoder)
|
| 297 |
+
self._aoti_compile_module(self.model.decoder, decoder_kwargs, decoder_dynamic)
|
| 298 |
+
self._aoti_compiled = True
|
| 299 |
+
print("AOTI: compiled backbone and decoder successfully.")
|
| 300 |
+
except Exception as exc:
|
| 301 |
+
self._aoti_compiled = True
|
| 302 |
+
print(f"AOTI: compilation failed, falling back to eager. Error: {exc}")
|
| 303 |
+
|
| 304 |
def preprocess(self, inputs: Dict[str, Any], cfg_scale: float):
|
| 305 |
self.load_heartmula()
|
| 306 |
tags = inputs["tags"]
|
|
|
|
| 439 |
offload_mode: str = "auto",
|
| 440 |
) -> Iterator[torch.Tensor]:
|
| 441 |
model_inputs = self.preprocess(inputs, cfg_scale=cfg_scale)
|
| 442 |
+
self._maybe_apply_aoti(model_inputs, cfg_scale)
|
| 443 |
self.load_heartcodec()
|
| 444 |
|
| 445 |
prompt_tokens = model_inputs["tokens"]
|
|
|
|
| 534 |
offload_mode: str = "auto",
|
| 535 |
):
|
| 536 |
model_inputs = self.preprocess(inputs, cfg_scale=cfg_scale)
|
| 537 |
+
self._maybe_apply_aoti(model_inputs, cfg_scale)
|
| 538 |
frames = self._generate_frames(model_inputs, max_audio_length_ms, temperature, topk, cfg_scale)
|
| 539 |
wav = self._decode_frames(frames)
|
| 540 |
torchaudio.save(save_path, wav, 48000)
|
|
|
|
| 552 |
lazy_load: bool = False,
|
| 553 |
use_deepspeed: bool = False,
|
| 554 |
ds_inference_config: Optional[Dict[str, Any]] = None,
|
| 555 |
+
torchao_quantize: bool = False,
|
| 556 |
+
aoti_enabled: Optional[bool] = None,
|
| 557 |
+
aoti_dynamic_shapes: Optional[bool] = None,
|
| 558 |
):
|
| 559 |
if os.path.isfile(vocab_path := os.path.join(pretrained_path, "tokenizer.json")):
|
| 560 |
tokenizer = Tokenizer.from_file(vocab_path)
|
|
|
|
| 601 |
lazy_load=lazy_load,
|
| 602 |
use_deepspeed=use_deepspeed,
|
| 603 |
ds_inference_config=ds_inference_config,
|
| 604 |
+
torchao_quantize=torchao_quantize,
|
| 605 |
+
aoti_enabled=aoti_enabled,
|
| 606 |
+
aoti_dynamic_shapes=aoti_dynamic_shapes,
|
| 607 |
)
|