196 lines
6.9 KiB
Python
196 lines
6.9 KiB
Python
# !/usr/bin/env python
|
||
# -*- coding: UTF-8 -*-
|
||
import os
|
||
import torch
|
||
import gc
|
||
import comfy.model_management as mm
|
||
from PIL import Image
|
||
import numpy as np
|
||
from comfy.utils import common_upscale
|
||
from datetime import datetime
|
||
import folder_paths
|
||
import soundfile as sf
|
||
import uuid
|
||
import hashlib
|
||
import json
|
||
from pathlib import Path
|
||
from transformers import AutoConfig
|
||
from safetensors.torch import load_file
|
||
from .SheetSage2.modeling_sheetsage2 import SheetSage2Model
|
||
cur_path = os.path.dirname(os.path.abspath(__file__))
|
||
|
||
def build_mert2_encoder(config, files):
|
||
from .SheetSage2.configuration_mert2 import MERT2Config
|
||
from .SheetSage2.modeling_mert2 import MERT2Model
|
||
from .SheetSage2.modeling_sheetsage2 import BASE_CODE_HASHES
|
||
encoder_config = MERT2Config(**config.backbone_config)
|
||
encoder_config._attn_implementation = config.encoder_attn_implementation
|
||
encoder = MERT2Model(encoder_config)
|
||
expected_hash = getattr(config, "base_model_sha256", None)
|
||
state = load_file(files)
|
||
keys = set(encoder.state_dict())
|
||
if not keys & set(state):
|
||
for prefix in ("model.", "encoder."):
|
||
stripped = {key[len(prefix):] if key.startswith(prefix) else key: value for key, value in state.items()}
|
||
if keys & set(stripped):
|
||
state = stripped
|
||
break
|
||
report = encoder.load_state_dict(state, strict=False)
|
||
del state
|
||
if report.missing_keys:
|
||
raise ValueError(f"MERT-v2 parent 权重不全,缺 {len(report.missing_keys)} 个键: {report.missing_keys[:8]}")
|
||
return encoder.to(dtype=torch.float32)
|
||
|
||
|
||
def loadsheetsage(model_file_path, mert2_file, dtype=torch.float32, device=torch.device("cpu")):
|
||
"""序列化加载 SheetSage2,不调用 from_pretrained。
|
||
|
||
- 权重含 encoder.*(merged 快照): 直接按 merged 配置构建后灌权重,无需父模型。
|
||
- 权重只有 adapter.*(原始发布版): 建 encoder 骨架 + 灌父权重 + merge_lora(必须在 CPU/float32 上合并)。
|
||
"""
|
||
config = AutoConfig.from_pretrained(os.path.join(cur_path, "SheetSage2"), trust_remote_code=True, local_files_only=True)
|
||
state = load_file(model_file_path)
|
||
if any(key.startswith("encoder.") for key in state):
|
||
config.weights_format = "merged" # 必须在构造之前改:__init__ 靠它决定建不建 encoder
|
||
model = SheetSage2Model(config)
|
||
model.load_state_dict(state, strict=False)
|
||
else:
|
||
model = SheetSage2Model(config) # adapter 分支: encoder=None, adapter=EncoderAdapters, lora_merged=False
|
||
model.load_state_dict(state, strict=False)
|
||
missing = [key for key in model.state_dict() if key.startswith("adapter.") and key not in state]
|
||
if missing:
|
||
raise ValueError(f"Adapter 权重不全,缺 {len(missing)} 个键: {missing[:8]}")
|
||
model.encoder = build_mert2_encoder(config, mert2_file)
|
||
model.merge_lora() # 把 adapter 的 LoRA 加进主干,并置 lora_merged=True
|
||
del state
|
||
model.to(dtype=torch.float32) # 合并/加载统一在 float32
|
||
if dtype is not None:
|
||
model.to(dtype=dtype)
|
||
if device is not None:
|
||
model.to(device)
|
||
return model.eval().requires_grad_(False)
|
||
|
||
|
||
def load_yue2_pipeline(repo="m-a-p/YuE2-3B",yue_ckpt=None,vae=None,device="cuda"):
|
||
from .src.yue2.pipeline import YuE2Pipeline
|
||
return YuE2Pipeline.from_pretrained(repo, yue_ckpt=yue_ckpt, vae=vae, device=device)
|
||
|
||
def yue2_generate(pipe,request,vae_model=None):
|
||
song = pipe(**request,vae_model=vae_model)
|
||
return song
|
||
|
||
|
||
def audio2path(audio,):
|
||
prefix=datetime.now().strftime("%Y%m%d_%H%M%S_%f")
|
||
audio_file = os.path.join(folder_paths.get_temp_directory(), f"audio_refer_temp_{prefix}.wav")
|
||
waveform = audio["waveform"].squeeze(0)
|
||
waveform_np = waveform.cpu().numpy() if hasattr(waveform, 'cpu') else waveform.numpy()
|
||
|
||
# 3. 格式转换:torchaudio 格式为 (channels, samples),soundfile 需要 (samples, channels)
|
||
# 如果是单声道音频 (1, samples),转置后变成 (samples, 1),符合 soundfile 的单声道要求
|
||
if waveform_np.ndim == 2:
|
||
waveform_np = waveform_np.T
|
||
|
||
sf.write(audio_file, waveform_np, audio["sample_rate"])
|
||
|
||
return audio_file
|
||
|
||
|
||
def clear_comfyui_cache():
|
||
cf_models=mm.loaded_models()
|
||
try:
|
||
for pipe in cf_models:
|
||
pipe.unpatch_model(device_to=torch.device("cpu"))
|
||
except: pass
|
||
mm.soft_empty_cache()
|
||
torch.cuda.empty_cache()
|
||
max_gpu_memory = torch.cuda.max_memory_allocated()
|
||
print(f"After Max GPU memory allocated: {max_gpu_memory / 1000 ** 3:.2f} GB")
|
||
|
||
def gc_cleanup():
|
||
gc.collect()
|
||
torch.cuda.empty_cache()
|
||
|
||
|
||
def phi2narry(img):
|
||
img = torch.from_numpy(np.array(img).astype(np.float32) / 255.0).unsqueeze(0)
|
||
return img
|
||
|
||
def tensor2image(tensor):
|
||
tensor = tensor.cpu()
|
||
image_np = tensor.squeeze().mul(255).clamp(0, 255).byte().numpy()
|
||
image = Image.fromarray(image_np, mode='RGB')
|
||
return image
|
||
|
||
def tensor2pillist(tensor_in):
|
||
d1, _, _, _ = tensor_in.size()
|
||
if d1 == 1:
|
||
img_list = [tensor2image(tensor_in)]
|
||
else:
|
||
tensor_list = torch.chunk(tensor_in, chunks=d1)
|
||
img_list=[tensor2image(i) for i in tensor_list]
|
||
return img_list
|
||
|
||
def tensor2pillist_upscale(tensor_in,width,height):
|
||
d1, _, _, _ = tensor_in.size()
|
||
if d1 == 1:
|
||
img_list = [nomarl_upscale(tensor_in,width,height)]
|
||
else:
|
||
tensor_list = torch.chunk(tensor_in, chunks=d1)
|
||
img_list=[nomarl_upscale(i,width,height) for i in tensor_list]
|
||
return img_list
|
||
|
||
def tensor2list(tensor_in,width,height):
|
||
if tensor_in is None:
|
||
return None
|
||
d1, _, _, _ = tensor_in.size()
|
||
if d1 == 1:
|
||
tensor_list = [tensor_upscale(tensor_in,width,height)]
|
||
else:
|
||
tensor_list_ = torch.chunk(tensor_in, chunks=d1)
|
||
tensor_list=[tensor_upscale(i,width,height) for i in tensor_list_]
|
||
return tensor_list
|
||
|
||
def tensor_upscale(tensor, width, height):
|
||
samples = tensor.movedim(-1, 1)
|
||
samples = common_upscale(samples, width, height, "bilinear", "center")
|
||
samples = samples.movedim(1, -1)
|
||
return samples
|
||
|
||
def nomarl_upscale(img, width, height):
|
||
samples = img.movedim(-1, 1)
|
||
img = common_upscale(samples, width, height, "bilinear", "center")
|
||
samples = img.movedim(1, -1)
|
||
img = tensor2image(samples)
|
||
return img
|
||
|
||
|
||
def map_0_1_to_neg1_1(t):
|
||
|
||
if not torch.is_tensor(t):
|
||
t = torch.tensor(t)
|
||
t = t.float()
|
||
|
||
try:
|
||
vmax = float(t.max())
|
||
except Exception:
|
||
vmax = 1.0
|
||
if vmax > 2.0:
|
||
t = t / 255.0
|
||
try:
|
||
vmin = float(t.min())
|
||
vmax = float(t.max())
|
||
except Exception:
|
||
vmin, vmax = -1.0, 1.0
|
||
if vmin >= 0.0 and vmax <= 1.1:
|
||
t = t * 2.0 - 1.0
|
||
return t
|
||
|
||
def map_neg1_1_to_0_1(t):
|
||
if not torch.is_tensor(t):
|
||
t = torch.tensor(t)
|
||
t = t.float()
|
||
t = (t + 1.0) * 0.5
|
||
t = t.clamp(0.0, 1.0)
|
||
return t
|