Files
smthemex-ComfyUI_YuE/yue_node.py
T
2025-02-24 11:10:35 +08:00

640 lines
33 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# !/usr/bin/env python
# -*- coding: UTF-8 -*-
import os
import torch
import gc
import numpy as np
from omegaconf import OmegaConf
import uuid
from tqdm import tqdm
import random
from einops import rearrange
import soundfile as sf
from transformers import AutoModelForCausalLM, LogitsProcessorList,BitsAndBytesConfig
from .inference.mmtokenizer import _MMSentencePieceTokenizer
from .inference.codecmanipulator import CodecManipulator
from .inference.infer import load_audio_mono,encode_audio,split_lyrics,BlockTokenRangeProcessor,stage2_inference,save_audio,seed_everything
from .inference.xcodec_mini_infer.vocoder import build_codec_model,process_audio
from .inference.xcodec_mini_infer.post_process_audio import replace_low_freq_with_energy_matched
from .inference.xcodec_mini_infer.models.soundstream_hubert_new import SoundStream
from .inference.infer_stage1 import Stage1Pipeline_EXL2,SampleSettings,Stage1Pipeline_HF
from .inference.infer_stage2 import Stage2Pipeline_EXL2 ,Stage2Pipeline_HF
from .inference.infer_postprocess import post_process
from mmgp import offload
import folder_paths
MAX_SEED = np.iinfo(np.int32).max
current_node_path = os.path.dirname(os.path.abspath(__file__))
device = torch.device(
"cuda:0") if torch.cuda.is_available() else torch.device("cpu")
# add checkpoints dir
YUE_weigths_path = os.path.join(folder_paths.models_dir, "yue")
if not os.path.exists(YUE_weigths_path):
os.makedirs(YUE_weigths_path)
folder_paths.add_model_folder_path("yue", YUE_weigths_path)
class YUE_Stage_A_Loader:
def __init__(self):
pass
@classmethod
def INPUT_TYPES(s):
ckpt_list_xcodec = [i for i in folder_paths.get_filename_list("yue") if "36" in i]
return {
"required": {
"stage_A_repo": ("STRING",{"default": "F:/test/ComfyUI/models/diffusers/Doctor-Shotgun/YuE-s1-7B-anneal-en-cot-exl2"},),
"xcodec_ckpt": (["none"] + ckpt_list_xcodec,),
"quantization_model":(["fp16","int8","int4","exllamav2"],),
"use_mmgp":("BOOLEAN",{"default":True}),
"stage1_cache_size": ("INT",{"default": 16384, "min": 8192, "max": MAX_SEED, "step": 64, "display": "number"}),
"exllamav2_cache_mode": (["FP16","Q8","Q6", "Q4"],),
"mmgp_profile": ([0,1,2,3,4,5],),
},
}
RETURN_TYPES = ("MODEL_YUE_A",)
RETURN_NAMES = ("model",)
FUNCTION = "loader_main"
CATEGORY = "YUE"
def loader_main(self, stage_A_repo, xcodec_ckpt,quantization_model,use_mmgp,stage1_cache_size,exllamav2_cache_mode,mmgp_profile):
basic_model_config=os.path.join(current_node_path, "inference/xcodec_mini_infer/final_ckpt/config.yaml")
model_config = OmegaConf.load(basic_model_config)
resume_path=folder_paths.get_full_path("yue", xcodec_ckpt)
if quantization_model=="exllamav2":
torch.autograd.grad_mode._enter_inference_mode(True)
torch.autograd.set_grad_enabled(False)
if exllamav2_cache_mode=="FP16":
print("**********Loading HF,fp16*********")
stage_1_model = Stage1Pipeline_HF(
model_path=stage_A_repo,
device=device,
basic_model_config=basic_model_config,
resume_path=resume_path,
cache_size=stage1_cache_size,
)
else:
print("**********Loading EXLLAMA V2*********")
stage_1_model = Stage1Pipeline_EXL2(
model_path=stage_A_repo,
device=device,
basic_model_config=basic_model_config,
resume_path=resume_path,
cache_size=stage1_cache_size,
cache_mode=exllamav2_cache_mode,
)
codectool_stage2=None
codectool=None
mmtokenizer=None
assert model_config.generator.name == "SoundStream"
codec_model = SoundStream(**model_config.generator.config).to(device)
parameter_dict = torch.load(resume_path, map_location=device, weights_only=False)
codec_model.load_state_dict(parameter_dict["codec_model"])
codec_model.eval()
else:
mmtokenizer = _MMSentencePieceTokenizer(os.path.join(current_node_path, "inference/mm_tokenizer_v0.2_hf/tokenizer.model"))
if quantization_model=="int8":
# Load 8-bit quantized model using bitsandbytes
print("**********Loading int8*********")
quantization_config = BitsAndBytesConfig(load_in_8bit=True)
stage_1_model = AutoModelForCausalLM.from_pretrained(
stage_A_repo,
torch_dtype=torch.bfloat16,
quantization_config=quantization_config,
attn_implementation="flash_attention_2", # To enable flashattn, you have to install flash-attn
)
elif quantization_model=="int4":
# Load 4-bit quantized model using bitsandbytes
print("**********Loading int4*********")
quantization_config = BitsAndBytesConfig(
load_in_4bit=True,
bnb_4bit_use_double_quant=True,
bnb_4bit_quant_type="int4"
)
stage_1_model = AutoModelForCausalLM.from_pretrained(
stage_A_repo,
torch_dtype=torch.bfloat16,
quantization_config=quantization_config,
attn_implementation="flash_attention_2",)
else:
# Load model without quantization
stage_1_model = AutoModelForCausalLM.from_pretrained(
stage_A_repo,
torch_dtype=torch.bfloat16,
attn_implementation="flash_attention_2", # To enable flashattn, you have to install flash-attn
)
if torch.__version__ >= "2.0.0":
stage_1_model = torch.compile(stage_1_model,mode="max-autotune")
# tokenizer = AutoTokenizer.from_pretrained(os.path.join(current_node_path, "mm_tokenizer_v0.2_hf"))
# stage_1_model = AutoModel.from_pretrained(folder_paths.get_full_path("yue", stage_A))
# to device, if gpu is available
if use_mmgp:
print("**********Loading mmpg *********")
stage_1_model.to("cpu")
else:
print("**********Loading fp16*********")
stage_1_model = stage_1_model.to('cuda:0')
stage_1_model.eval()
if use_mmgp:
pipe = { "transformer": stage_1_model,}
kwargs = {}
if mmgp_profile == 4 :
kwargs["budgets"] = { "transformer": 3000, "*" : 5000 }
elif mmgp_profile == 2:
kwargs["budgets"] = 5000
compile= True
quantizeTransformer = mmgp_profile == 3 or mmgp_profile == 4 or mmgp_profile == 5
offload.profile(pipe, profile_no = mmgp_profile, quantizeTransformer= quantizeTransformer, compile = compile, verboseLevel= 1, **kwargs )
codectool = CodecManipulator("xcodec", 0, 1)
codectool_stage2 = CodecManipulator("xcodec", 0, 8)
codec_model = eval(model_config.generator.name)(**model_config.generator.config).to(device)
parameter_dict = torch.load(resume_path, map_location='cpu', weights_only=False)
codec_model.load_state_dict(parameter_dict['codec_model'])
codec_model.to(device)
codec_model.eval()
torch.cuda.empty_cache()
gc.collect()
return ({"stage_1_model":stage_1_model,"codectool_stage2":codectool_stage2,"codectool":codectool,"codec_model":codec_model,"mmtokenizer":mmtokenizer,"quantization_model":quantization_model,"mmgp_profile":mmgp_profile},)
class YUE_Stage_A_Sampler:
def __init__(self):
pass
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"model": ("MODEL_YUE_A",),
"genres_prompt": ("STRING", {"default": "inspiring female uplifting pop airy vocal electronic bright vocal vocal.", "multiline": False}),
"lyrics_prompt": ("STRING", {"default":
"[verse]\nStaring at the sunset, colors paint the sky.\nThoughts of you keep swirling, can't deny.\nI know I let you down, I made mistakes.\nBut I'm here to mend the heart I didn't break.\n\n"
"[chorus]\nEvery road you take, I'll be one step behind.\nEvery dream you chase, I'm reaching for the light.\nYou can't fight this feeling now.\nI won't back down.\nYou know you can't deny it now.\n I won't back down \n\n"
"[verse]\nThey might say I'm foolish, chasing after you.\nBut they don't feel this love the way we do.\nMy heart beats only for you, can't you see?\nI won't let you slip away from me. \n\n"
"[chorus]\nEvery road you take, I'll be one step behind.\nEvery dream you chase, I'm reaching for the light.\nYou can't fight this feeling now.\nI won't back down.\nYou know you can't deny it now.\n I won't back down \n\n"
"[bridge]\nNo, I won't back down, won't turn around.\nUntil you're back where you belong.\nI'll cross the oceans wide, stand by your side.\nTogether we are strong. \n\n"
"[outro]\nEvery road you take, I'll be one step behind.\nEvery dream you chase, love's the tie that binds.\nYou can't fight this feeling now.\nI won't back down.", "multiline": True}),
"seed": ("INT", {"default": 0, "min": 0, "max": MAX_SEED}),
"run_n_segment": ("INT", {"default": 2, "min": 1, "max": 10, "step": 1, "display": "number"}),
"repetition_penalty": ("FLOAT", {"default": 1.1, "min": 1.0, "max": 2.0, "step": 0.1}),
"prompt_start_time": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 1024.0, "step": 0.5}),
"prompt_end_time": ("FLOAT", {"default": 30.0, "min": 1.0, "max": 1024.0, "step": 0.5}),
"max_new_tokens": ("INT", {"default": 3000, "min": 2944, "max": 16384, "step": 1, "display": "number"}),
"use_dual_tracks_prompt":("BOOLEAN",{"default":True}),
"use_audio_prompt":("BOOLEAN",{"default":True}),
"offload_model":("BOOLEAN",{"default":True}),
"stage1_no_guidance":("BOOLEAN",{"default":True}),
}}
RETURN_TYPES = ("STAGE_SET", "quantization_model",)
RETURN_NAMES = ("stage1_set","info")
FUNCTION = "sampler_main"
CATEGORY = "YUE"
def sampler_main(self, model,genres_prompt, lyrics_prompt,seed, run_n_segment, repetition_penalty,prompt_start_time,prompt_end_time,max_new_tokens,
use_dual_tracks_prompt,use_audio_prompt,offload_model,stage1_no_guidance):
instrumental_track_prompt_path=os.path.join(current_node_path, "prompt_egs/pop.00001.Instrumental.mp3")
vocal_track_prompt_path=os.path.join(current_node_path, "prompt_egs/pop.00001.Vocals.mp3")
audio_prompt_path=os.path.join(current_node_path, "prompt_egs/pop.00001.mp3")
stage1_output_dir=os.path.join(folder_paths.get_output_directory(), "stage1")
os.makedirs(stage1_output_dir, exist_ok=True)
#genre_txt_path=os.path.join(current_node_path, "prompt_egs/genre.txt")
#lyrics_txt_path=os.path.join(current_node_path, "prompt_egs/lyrics.txt")
quantization_model=model.get("quantization_model")
mmtokenizer=model.get("mmtokenizer")
codec_model=model.get("codec_model")
codectool=model.get("codectool")
codectool_stage2=model.get("codectool_stage2")
mmgp_profile=model.get("mmgp_profile")
if quantization_model=="exllamav2":
# with open(lyrics_txt_path) as f:
# lyrics = f.read().strip()
lyrics=lyrics_prompt.strip()
pipeline=model.get("stage_1_model")
seed_everything(seed)
raw_output = pipeline.generate(
use_dual_tracks_prompt=use_dual_tracks_prompt,
vocal_track_prompt_path=vocal_track_prompt_path,
instrumental_track_prompt_path=instrumental_track_prompt_path,
use_audio_prompt=use_audio_prompt,
audio_prompt_path=audio_prompt_path,
genres=genres_prompt.strip(),
lyrics=lyrics,
run_n_segments=run_n_segment,
max_new_tokens=max_new_tokens,
prompt_start_time=prompt_start_time,
prompt_end_time=prompt_end_time,
sample_settings=SampleSettings(use_guidance=not stage1_no_guidance, repetition_penalty=repetition_penalty),
)
#print(raw_output)
# Save result
pipeline.save(raw_output, folder_paths.get_output_directory(), use_audio_prompt, use_dual_tracks_prompt)
stage1_output_set=None
else:
# with open(genre_txt_path) as f:
# genres_prompt = f.read().strip()
# with open(lyrics_txt_path) as f:
# lyrics_prompt = split_lyrics(f.read())
lyrics=split_lyrics(lyrics_prompt.strip())
stage_1_model=model.get("stage_1_model")
seed_everything(seed)
# Call the function and print the result
stage1_output_set = []
# Tips:
# genre tags support instrumental,genre,mood,vocal timbr and vocal gender
# all kinds of tags are needed
# intruction
full_lyrics = "\n".join(lyrics)
prompt_texts = [f"Generate music from the given lyrics segment by segment.\n[Genre] {genres_prompt}\n{full_lyrics}"]
prompt_texts += lyrics
random_id = uuid.uuid4()
output_seq = None
# Here is suggested decoding config
top_p = 0.93
temperature = 1.0
#repetition_penalty = args.repetition_penalty
# special tokens
start_of_segment = mmtokenizer.tokenize('[start_of_segment]')
end_of_segment = mmtokenizer.tokenize('[end_of_segment]')
# Format text prompt
#run_n_segments = min(run_n_segment+1, len(lyrics_prompt))
run_n_segments = min(run_n_segment, len(lyrics_prompt))
for i, p in enumerate(tqdm(prompt_texts[:run_n_segments], desc="Stage1 inference...")):
section_text = p.replace('[start_of_segment]', '').replace('[end_of_segment]', '')
guidance_scale = 1.5 if i <=1 else 1.2
if i==0:
continue
if i==1:
if use_dual_tracks_prompt or use_audio_prompt:
if use_dual_tracks_prompt:
vocals_ids = load_audio_mono(vocal_track_prompt_path)
instrumental_ids = load_audio_mono(instrumental_track_prompt_path)
vocals_ids = encode_audio(codec_model, vocals_ids, device, target_bw=0.5)
instrumental_ids = encode_audio(codec_model, instrumental_ids, device, target_bw=0.5)
vocals_ids = codectool.npy2ids(vocals_ids[0])
instrumental_ids = codectool.npy2ids(instrumental_ids[0])
ids_segment_interleaved = rearrange([np.array(vocals_ids), np.array(instrumental_ids)], 'b n -> (n b)')
audio_prompt_codec = ids_segment_interleaved[int(prompt_start_time*50*2): int(prompt_end_time*50*2)]
audio_prompt_codec = audio_prompt_codec.tolist()
elif use_audio_prompt:
audio_prompt = load_audio_mono(audio_prompt_path)
raw_codes = encode_audio(codec_model, audio_prompt, device, target_bw=0.5)
# Format audio prompt
code_ids = codectool.npy2ids(raw_codes[0])
audio_prompt_codec = code_ids[int(prompt_start_time *50): int(prompt_end_time *50)] # 50 is tps of xcodec
audio_prompt_codec_ids = [mmtokenizer.soa] + codectool.sep_ids + audio_prompt_codec + [mmtokenizer.eoa]
sentence_ids = mmtokenizer.tokenize("[start_of_reference]") + audio_prompt_codec_ids + mmtokenizer.tokenize("[end_of_reference]")
head_id = mmtokenizer.tokenize(prompt_texts[0]) + sentence_ids
else:
head_id = mmtokenizer.tokenize(prompt_texts[0])
prompt_ids = head_id + start_of_segment + mmtokenizer.tokenize(section_text) + [mmtokenizer.soa] + codectool.sep_ids
else:
prompt_ids = end_of_segment + start_of_segment + mmtokenizer.tokenize(section_text) + [mmtokenizer.soa] + codectool.sep_ids
prompt_ids = torch.as_tensor(prompt_ids).unsqueeze(0).to(device)
input_ids = torch.cat([raw_output, prompt_ids], dim=1) if i > 1 else prompt_ids
# Use window slicing in case output sequence exceeds the context of model
max_context = 16384-max_new_tokens-1
if input_ids.shape[-1] > max_context:
print(f'Section {i}: output length {input_ids.shape[-1]} exceeding context length {max_context}, now using the last {max_context} tokens.')
input_ids = input_ids[:, -(max_context):]
with torch.no_grad():
output_seq = stage_1_model.generate(
input_ids=input_ids,
max_new_tokens=max_new_tokens,
min_new_tokens=100,
do_sample=True,
top_p=top_p,
temperature=temperature,
repetition_penalty=repetition_penalty,
eos_token_id=mmtokenizer.eoa,
pad_token_id=mmtokenizer.eoa,
logits_processor=LogitsProcessorList([BlockTokenRangeProcessor(0, 32002), BlockTokenRangeProcessor(32016, 32016)]),
guidance_scale=guidance_scale,
)
if output_seq[0][-1].item() != mmtokenizer.eoa:
tensor_eoa = torch.as_tensor([[mmtokenizer.eoa]]).to(stage_1_model.device)
output_seq = torch.cat((output_seq, tensor_eoa), dim=1)
if i > 1:
raw_output = torch.cat([raw_output, prompt_ids, output_seq[:, input_ids.shape[-1]:]], dim=1)
else:
raw_output = output_seq
# save raw output and check sanity
ids = raw_output[0].cpu().numpy()
soa_idx = np.where(ids == mmtokenizer.soa)[0].tolist()
eoa_idx = np.where(ids == mmtokenizer.eoa)[0].tolist()
if len(soa_idx)!=len(eoa_idx):
raise ValueError(f'invalid pairs of soa and eoa, Num of soa: {len(soa_idx)}, Num of eoa: {len(eoa_idx)}')
vocals = []
instrumentals = []
range_begin = 1 if use_audio_prompt or use_dual_tracks_prompt else 0
for i in range(range_begin, len(soa_idx)):
codec_ids = ids[soa_idx[i]+1:eoa_idx[i]]
if codec_ids[0] == 32016:
codec_ids = codec_ids[1:]
codec_ids = codec_ids[:2 * (codec_ids.shape[0] // 2)]
vocals_ids = codectool.ids2npy(rearrange(codec_ids,"(n b) -> b n", b=2)[0])
vocals.append(vocals_ids)
instrumentals_ids = codectool.ids2npy(rearrange(codec_ids,"(n b) -> b n", b=2)[1])
instrumentals.append(instrumentals_ids)
vocals = np.concatenate(vocals, axis=1)
instrumentals = np.concatenate(instrumentals, axis=1)
vocal_save_path = os.path.join(stage1_output_dir, f"{genres_prompt.replace(' ', '-')}_tp{top_p}_T{temperature}_rp{repetition_penalty}_maxtk{max_new_tokens}_{random_id}_vtrack".replace('.', '@')+'.npy')
inst_save_path = os.path.join(stage1_output_dir, f"{genres_prompt.replace(' ', '-')}_tp{top_p}_T{temperature}_rp{repetition_penalty}_maxtk{max_new_tokens}_{random_id}_itrack".replace('.', '@')+'.npy')
np.save(vocal_save_path, vocals)
np.save(inst_save_path, instrumentals)
stage1_output_set.append(vocal_save_path)
stage1_output_set.append(inst_save_path)
# offload model
if offload_model:
stage_1_model.cpu()
stage_1_model=None
torch.cuda.empty_cache()
gc.collect()
torch.cuda.empty_cache()
gc.collect()
return ({"stage1_output_set":stage1_output_set,"codec_model":codec_model,"codectool_stage2":codectool_stage2,"mmtokenizer":mmtokenizer,"codectool":codectool,"quantization_model":quantization_model,},{"quantization_model":quantization_model,"mmgp_profile":mmgp_profile,},)
class YUE_Stage_B_Loader:
def __init__(self):
pass
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"info": ("quantization_model",),
"stage_B_repo": ("STRING",{"default": "F:/test/ComfyUI/models/diffusers/Doctor-Shotgun/YuE-s2-1B-general-exl2"},),
"stage2_cache_size": ("INT",{"default": 8192, "min": 4096, "max": MAX_SEED, "step": 64, "display": "number"}),
"stage2_batch_size": ("INT",{"default": 2, "min": 1, "max": 64, "step": 1, "display": "number"}),
"exllamav2_cache_mode": (["FP16","Q8","Q6", "Q4"],),
"use_mmgp":("BOOLEAN",{"default":True}),
},
}
RETURN_TYPES = ("MODEL_YUE_B",)
RETURN_NAMES = ("model",)
FUNCTION = "loader_main"
CATEGORY = "YUE"
def loader_main(self,info,stage_B_repo,stage2_cache_size,stage2_batch_size,exllamav2_cache_mode,use_mmgp):
quantization_model=info.get("quantization_model")
mmgp_profile=info.get("mmgp_profile")
if not use_mmgp:
if quantization_model=="exllamav2":
if exllamav2_cache_mode=="FP16":
print("**********Loading fp16*********")
model_stage2=Stage2Pipeline_HF(model_path=stage_B_repo, device=device, batch_size=stage2_batch_size)
else:
print("**********Loading exllamav2*********")
model_stage2=Stage2Pipeline_EXL2(model_path=stage_B_repo, device=device, cache_size=stage2_cache_size, cache_mode=exllamav2_cache_mode)
else:
if quantization_model=="int8":
print("**********Loading int8*********")
quantization_config = BitsAndBytesConfig(load_in_8bit=True)
model_stage2 = AutoModelForCausalLM.from_pretrained(
stage_B_repo,
torch_dtype=torch.bfloat16,
quantization_config=quantization_config,
attn_implementation="flash_attention_2",
#device_map="auto"
)
elif quantization_model=="int4":
print("**********Loading int4*********")
quantization_config = BitsAndBytesConfig(
load_in_4bit=True,
bnb_4bit_use_double_quant=True,
bnb_4bit_quant_type="int4"
)
model_stage2 = AutoModelForCausalLM.from_pretrained(
stage_B_repo,
torch_dtype=torch.bfloat16,
quantization_config=quantization_config,
attn_implementation="flash_attention_2",)
else:
print("**********Loading fp16*********")
model_stage2 = AutoModelForCausalLM.from_pretrained(
stage_B_repo,
torch_dtype=torch.bfloat16,
attn_implementation="flash_attention_2",
# device_map="auto",
)
if torch.__version__ >= "2.0.0":
model_stage2 = torch.compile(model_stage2)
if quantization_model=="fp16":
model_stage2 = model_stage2.to('cuda:0')
model_stage2.eval()
else:
print("**********Loading mmpg*********")
model_stage2 = AutoModelForCausalLM.from_pretrained(
stage_B_repo,
torch_dtype=torch.float16,
attn_implementation="flash_attention_2"
)
if torch.__version__ >= "2.0.0":
model_stage2 = torch.compile(model_stage2)
model_stage2.to("cpu")
model_stage2.eval()
compile= True
pipe = { "stage2": model_stage2,}
kwargs = {}
if mmgp_profile == 4 :
kwargs["budgets"] = { "transformer": 3000, "*" : 5000 }
elif mmgp_profile == 2:
kwargs["budgets"] = 5000
quantizeTransformer = mmgp_profile == 3 or mmgp_profile == 4 or mmgp_profile == 5
offload.profile(pipe, profile_no = mmgp_profile, compile = compile, quantizeTransformer= quantizeTransformer, verboseLevel= 1, **kwargs )
gc.collect()
torch.cuda.empty_cache()
return ({"model_stage2":model_stage2,"stage2_batch_size":stage2_batch_size},)
class YUE_Stage_B_Sampler:
def __init__(self):
pass
@classmethod
def INPUT_TYPES(s):
ckpt_list_vocal = [i for i in folder_paths.get_filename_list("yue") if "131" in i]
ckpt_list_inst = [i for i in folder_paths.get_filename_list("yue") if "151" in i]
return {
"required": {
"stage1_set": ("STAGE_SET",),
"model": ("MODEL_YUE_B",),
"vocal_decoder_ckpt": (["none"] + ckpt_list_vocal,),
"inst_decoder_ckpt": (["none"] + ckpt_list_inst,),
"rescale":("BOOLEAN",{"default":True}),
}}
RETURN_TYPES = ("AUDIO","STRING", )
RETURN_NAMES = ("audio","string",)
FUNCTION = "sampler_main"
CATEGORY = "YUE"
def sampler_main(self,stage1_set, model,vocal_decoder_ckpt, inst_decoder_ckpt,rescale):
file_prefix = ''.join(random.choice("0123456789") for _ in range(6))
quantization_model=stage1_set.get("quantization_model")
model_stage2=model.get("model_stage2")
stage2_batch_size=model.get("stage2_batch_size")
config_path=os.path.join(current_node_path,'inference/xcodec_mini_infer/decoders/config.yaml')
vocal_decoder_path=folder_paths.get_full_path("yue", vocal_decoder_ckpt)
inst_decoder_path=folder_paths.get_full_path("yue", inst_decoder_ckpt)
output_dir=folder_paths.get_output_directory()
if quantization_model=="exllamav2":
outputs = model_stage2.generate(output_dir=output_dir)
model_stage2.save(output_dir=output_dir, outputs=outputs)
codec_model=stage1_set.get("codec_model")
mix_output,c_file=post_process(codec_model, device, output_dir, config_path, vocal_decoder_path, inst_decoder_path, rescale,file_prefix)
else:
stage1_output_set=stage1_set.get("stage1_output_set")
codectool_stage2=stage1_set.get("codectool_stage2")
codectool=stage1_set.get("codectool")
codec_model=stage1_set.get("codec_model")
mmtokenizer=stage1_set.get("mmtokenizer")
stage2_output_dir=os.path.join(folder_paths.get_output_directory(), "stage2")
os.makedirs(stage2_output_dir, exist_ok=True)
stage2_result = stage2_inference(model_stage2, stage1_output_set, stage2_output_dir,codectool_stage2,mmtokenizer,codectool,device, batch_size=stage2_batch_size)
print(stage2_result)
print('Stage 2 DONE.\n')
# reconstruct tracks
recons_output_dir = os.path.join(output_dir, "recons")
recons_mix_dir = os.path.join(recons_output_dir, 'mix')
os.makedirs(recons_mix_dir, exist_ok=True)
tracks = []
for npy in stage2_result:
codec_result = np.load(npy)
decodec_rlt=[]
with torch.no_grad():
decoded_waveform = codec_model.decode(torch.as_tensor(codec_result.astype(np.int16), dtype=torch.long).unsqueeze(0).permute(1, 0, 2).to(device))
decoded_waveform = decoded_waveform.cpu().squeeze(0)
decodec_rlt.append(torch.as_tensor(decoded_waveform))
decodec_rlt = torch.cat(decodec_rlt, dim=-1)
save_path = os.path.join(recons_output_dir, os.path.splitext(os.path.basename(npy))[0] + ".mp3")
tracks.append(save_path)
save_audio(decodec_rlt, save_path, 16000)
# mix tracks
for inst_path in tracks:
try:
if (inst_path.endswith('.wav') or inst_path.endswith('.mp3')) \
and '_itrack' in inst_path:
# find pair
vocal_path = inst_path.replace('_itrack', '_vtrack')
if not os.path.exists(vocal_path):
continue
# mix
recons_mix = os.path.join(recons_mix_dir, os.path.basename(inst_path).replace('_itrack', '_mixed'))
vocal_stem, sr = sf.read(inst_path)
instrumental_stem, _ = sf.read(vocal_path)
mix_stem = (vocal_stem + instrumental_stem) / 1
sf.write(recons_mix, mix_stem, sr)
except Exception as e:
print(e)
# vocoder to upsample audios
vocal_decoder, inst_decoder = build_codec_model(config_path, vocal_decoder_path, inst_decoder_path)
vocoder_output_dir = os.path.join(output_dir, 'vocoder')
vocoder_stems_dir = os.path.join(vocoder_output_dir, 'stems')
vocoder_mix_dir = os.path.join(vocoder_output_dir, 'mix')
os.makedirs(vocoder_mix_dir, exist_ok=True)
os.makedirs(vocoder_stems_dir, exist_ok=True)
args={}
for npy in stage2_result:
if '_itrack' in npy:
# Process instrumental
instrumental_output = process_audio(
npy,
os.path.join(vocoder_stems_dir, 'itrack.mp3'),
rescale,
args,
inst_decoder,
codec_model
)
else:
# Process vocal
vocal_output = process_audio(
npy,
os.path.join(vocoder_stems_dir, 'vtrack.mp3'),
rescale,
args,
vocal_decoder,
codec_model
)
# mix tracks
try:
mix_output = instrumental_output + vocal_output
vocoder_mix = os.path.join(vocoder_mix_dir, os.path.basename(recons_mix))
save_audio(mix_output, vocoder_mix, 44100, rescale)
print(f"Created mix: {vocoder_mix}")
except RuntimeError as e:
print(e)
print(f"mix {vocoder_mix} failed! inst: {instrumental_output.shape}, vocal: {vocal_output.shape}")
c_file=os.path.join(output_dir, f"yue_{file_prefix}_{os.path.basename(recons_mix)}")
# Post process
replace_low_freq_with_energy_matched(
a_file=recons_mix, # 16kHz
b_file=vocoder_mix, # 48kHz
c_file=c_file,
cutoff_freq=5500.0
)
print(mix_output.shape)
audio= {"waveform": mix_output.unsqueeze(0), "sample_rate": 44100}
return(audio,c_file,)
NODE_CLASS_MAPPINGS = {
"YUE_Stage_A_Loader": YUE_Stage_A_Loader,
"YUE_Stage_A_Sampler": YUE_Stage_A_Sampler,
"YUE_Stage_B_Loader": YUE_Stage_B_Loader,
"YUE_Stage_B_Sampler": YUE_Stage_B_Sampler,
}
NODE_DISPLAY_NAME_MAPPINGS = {
"YUE_Stage_A_Loader": "YUE_Stage_A_Loader",
"YUE_Stage_A_Sampler": "YUE_Stage_A_Sampler",
"YUE_Stage_B_Loader": "YUE_Stage_B_Loader",
"YUE_Stage_B_Sampler": "YUE_Stage_B_Sampler",
}