Compare commits

..
15 Commits
14 changed files with 108 additions and 76 deletions
Binary file not shown.

Before

Width:  |  Height:  |  Size: 822 KiB

After

Width:  |  Height:  |  Size: 508 KiB

Binary file not shown.
+2 -1
View File
@@ -114,7 +114,8 @@ def process(args, config, model: torch.nn.Module):
) )
generated_audio = generated_audio.squeeze().cpu().numpy() generated_audio = generated_audio.squeeze().cpu().numpy()
generated_merged[start_sample_idx : start_sample_idx + generated_audio.shape[0]] = generated_audio gen_len = min(generated_audio.shape[0], generated_merged.shape[0] - start_sample_idx)
generated_merged[start_sample_idx: start_sample_idx + gen_len] = generated_audio[:gen_len]
merged_path = os.path.join(args.save_dir, "generated.wav") merged_path = os.path.join(args.save_dir, "generated.wav")
sf.write(merged_path, generated_merged, 24000) sf.write(merged_path, generated_merged, 24000)
+1
View File
@@ -23,6 +23,7 @@ sageattention==1.0.6
scikit_learn==1.7.2 scikit_learn==1.7.2
scipy==1.15.3 scipy==1.15.3
six==1.17.0 six==1.17.0
setuptools==81.0.0
scikit_image==0.25.2 scikit_image==0.25.2
soundfile==0.13.1 soundfile==0.13.1
ToJyutping==3.2.0 ToJyutping==3.2.0
+3 -3
View File
@@ -516,12 +516,12 @@ class F0Extractor:
if __name__ == "__main__": if __name__ == "__main__":
model_path = ( model_path = (
"pretrained_models/rmvpe/rmvpe.pt" "pretrained_models/SoulX-Singer-Preprocess/rmvpe/rmvpe.pt"
) )
audio_path = "./outputs/transcription/test.wav" audio_path = "example/audio/zh_prompt.mp3"
pe = F0Extractor( pe = F0Extractor(
model_path, model_path,
device="cuda", device="cuda",
) )
f0 = pe.process(audio_path) f0 = pe.process(audio_path, f0_path="example/audio/zh_prompt_f0.npy")
+13 -9
View File
@@ -120,9 +120,10 @@ class _ASRZhModel:
raw_timestamps = [[t[0] / 1000, t[1] / 1000] for t in out["timestamp"]] raw_timestamps = [[t[0] / 1000, t[1] / 1000] for t in out["timestamp"]]
words, word_durs = _build_words_with_gaps(raw_words, raw_timestamps, wav_fn) words, word_durs = _build_words_with_gaps(raw_words, raw_timestamps, wav_fn)
if os.path.exists(wav_fn.replace(".wav", "_f0.npy")): f0_path = os.path.splitext(wav_fn)[0] + "_f0.npy"
if os.path.exists(f0_path):
words, word_durs = _word_dur_post_process( words, word_durs = _word_dur_post_process(
words, word_durs, np.load(wav_fn.replace(".wav", "_f0.npy")) words, word_durs, np.load(f0_path)
) )
return words, word_durs return words, word_durs
@@ -179,9 +180,10 @@ class _ASREnModel:
words, durs = _build_words_with_gaps(raw_words, raw_timestamps, wav_fn) words, durs = _build_words_with_gaps(raw_words, raw_timestamps, wav_fn)
if os.path.exists(wav_fn.replace(".wav", "_f0.npy")): f0_path = os.path.splitext(wav_fn)[0] + "_f0.npy"
if os.path.exists(f0_path):
words, durs = _word_dur_post_process( words, durs = _word_dur_post_process(
words, durs, np.load(wav_fn.replace(".wav", "_f0.npy")) words, durs, np.load(f0_path)
) )
return words, durs return words, durs
@@ -248,8 +250,10 @@ class LyricTranscriber:
if self.en_model is None: if self.en_model is None:
# Lazy-load NeMo model only when English is actually used. # Lazy-load NeMo model only when English is actually used.
if v: if v:
print("[lyric transcription] init English ASR, please make sure NeMo is installed") print("[lyric transcription] init English ASR start, please make sure NeMo is installed and wait for a while")
self.en_model = _ASREnModel(model_path=self.en_model_path, device=self.device) self.en_model = _ASREnModel(model_path=self.en_model_path, device=self.device)
if v:
print("[lyric transcription] init English ASR success")
out = self.en_model.process(wav_fn) out = self.en_model.process(wav_fn)
else: else:
out = self.zh_model.process(wav_fn) out = self.zh_model.process(wav_fn)
@@ -271,9 +275,9 @@ class LyricTranscriber:
if __name__ == "__main__": if __name__ == "__main__":
m = LyricTranscriber( m = LyricTranscriber(
zh_model_path="pretrained_models/speech_seaco_paraformer_large_asr_nat-zh-cn-16k-common-vocab8404-pytorch", zh_model_path="pretrained_models/SoulX-Singer-Preprocess/speech_seaco_paraformer_large_asr_nat-zh-cn-16k-common-vocab8404-pytorch",
en_model_path="pretrained_models/parakeet-tdt-0.6b-v2/parakeet-tdt-0.6b-v2.nemo", en_model_path="pretrained_models/SoulX-Singer-Preprocess/parakeet-tdt-0.6b-v2/parakeet-tdt-0.6b-v2.nemo",
device="cuda" device="cuda"
) )
print(m.process("example/test/asr_zh.wav", language="Mandarin")) print(m.process("example/audio/zh_prompt.mp3", language="Mandarin"))
print(m.process("example/test/asr_en.wav", language="English")) print(m.process("example/audio/en_prompt.mp3", language="English"))
+14 -5
View File
@@ -509,14 +509,23 @@ class NoteTranscriber:
if __name__ == "__main__": if __name__ == "__main__":
items = json.load(open("example/test/rosvot_input.json", "r")) item = {
item = items[0] 'item_name': 'vocal_0',
'wav_fn': 'example/audio/zh_prompt.mp3',
'start_time_ms': 320,
'end_time_ms': 10687,
'origin_wav_fn': 'example/audio/zh_prompt.mp3',
'duration': 10367,
'words': ['<SP>', '除', '了', '想', '你', '<SP>', '除', '了', '爱', '你', '<SP>', '我', '什', '么', '什', '么', '都', '愿', '意'],
'word_durs': [0.21, 0.36, 0.26, 0.7000000000000001, 0.96, 0.3800000000000001, 0.43999999999999995, 0.3799999999999999, 0.6400000000000001, 0.9600000000000002, 1.1199999999999999, 0.28000000000000025, 0.3799999999999999, 0.3199999999999994, 0.3200000000000003, 0.3799999999999999, 0.3200000000000003, 0.5, 1.457981859410431],
'language': 'Mandarin'
}
m = NoteTranscriber( m = NoteTranscriber(
rosvot_model_path="pretrained_models/rosvot/rosvot/model.pt", rosvot_model_path="pretrained_models/SoulX-Singer-Preprocess/rosvot/rosvot/model.pt",
rwbd_model_path="pretrained_models/rosvot/rwbd/model.pt", rwbd_model_path="pretrained_models/SoulX-Singer-Preprocess/rosvot/rwbd/model.pt",
device="cuda" device="cuda"
) )
out = m.process(item) out = m.process(item, segment_info=item)
print(out) print(out)
+4 -1
View File
@@ -289,6 +289,9 @@ class VocalDetector:
key = f"{base_name_no_ext}_{idx}" key = f"{base_name_no_ext}_{idx}"
start_sample = librosa.time_to_samples(start_ms / 1000, sr=sr) start_sample = librosa.time_to_samples(start_ms / 1000, sr=sr)
end_sample = librosa.time_to_samples(end_ms / 1000, sr=sr) end_sample = librosa.time_to_samples(end_ms / 1000, sr=sr)
# Use Python int to avoid 32-bit overflow on Windows
start_sample = int(start_sample)
end_sample = int(end_sample)
segment = y[start_sample:end_sample] segment = y[start_sample:end_sample]
write(f"{self.cut_wavs_output_dir}/{key}.wav", segment, sr) write(f"{self.cut_wavs_output_dir}/{key}.wav", segment, sr)
@@ -316,5 +319,5 @@ class VocalDetector:
if __name__ == "__main__": if __name__ == "__main__":
m = VocalDetector(cut_wavs_output_dir="outputs/transcription/cut_wavs") m = VocalDetector(cut_wavs_output_dir="outputs/transcription/cut_wavs")
segment_infos = m.process("./outputs/transcription/test.wav") segment_infos = m.process("example/audio/zh_prompt.mp3", np.load("example/audio/zh_prompt_f0.npy"))
print(segment_infos) print(segment_infos)
+5 -5
View File
@@ -226,12 +226,12 @@ class VocalSeparator:
if __name__ == "__main__": if __name__ == "__main__":
m = VocalSeparator( m = VocalSeparator(
sep_model_path="pretrained_models/mel-band-roformer-karaoke/mel_band_roformer_karaoke_becruily.ckpt", sep_model_path="pretrained_models/SoulX-Singer-Preprocess/mel-band-roformer-karaoke/mel_band_roformer_karaoke_becruily.ckpt",
sep_config_path="pretrained_models/mel-band-roformer-karaoke/config_karaoke_becruily.yaml", sep_config_path="pretrained_models/SoulX-Singer-Preprocess/mel-band-roformer-karaoke/config_karaoke_becruily.yaml",
der_model_path="pretrained_models/dereverb_mel_band_roformer/dereverb_mel_band_roformer_anvuew_sdr_19.1729.ckpt", der_model_path="pretrained_models/SoulX-Singer-Preprocess/dereverb_mel_band_roformer/dereverb_mel_band_roformer_anvuew_sdr_19.1729.ckpt",
der_config_path="pretrained_models/dereverb_mel_band_roformer/dereverb_mel_band_roformer_anvuew.yaml", der_config_path="pretrained_models/SoulX-Singer-Preprocess/dereverb_mel_band_roformer/dereverb_mel_band_roformer_anvuew.yaml",
device="cuda" device="cuda"
) )
out = m.process("example/test/separation_test.mp3") out = m.process("example/audio/zh_prompt.mp3")
print(out.vocals_dereverbed.shape) print(out.vocals_dereverbed.shape)
+9 -9
View File
@@ -109,10 +109,11 @@ def _merge_group(
) )
# ---------- time & audio ---------- # ---------- time & audio ----------
start_ms = get_attr(segments[0], "start_time_ms", 0) # Use Python int to avoid 32-bit overflow on Windows
end_ms = get_attr(segments[-1], "end_time_ms", 0) + end_extension_ms start_ms = int(get_attr(segments[0], "start_time_ms", 0))
start_sample = start_ms * sample_rate // 1000 end_ms = int(get_attr(segments[-1], "end_time_ms", 0)) + end_extension_ms
end_sample = end_ms * sample_rate // 1000 start_sample = int(start_ms * sample_rate // 1000)
end_sample = int(end_ms * sample_rate // 1000)
# ---------- naming ---------- # ---------- naming ----------
first_item_name = get_attr(segments[0], "item_name", "segment") first_item_name = get_attr(segments[0], "item_name", "segment")
@@ -195,12 +196,11 @@ def merge_short_segments(
for seg in segments: for seg in segments:
if isinstance(seg, dict): if isinstance(seg, dict):
start_time = seg.get("start_time_ms", 0) start_time = int(seg.get("start_time_ms", 0))
end_time = seg.get("end_time_ms", 0) end_time = int(seg.get("end_time_ms", 0))
else: else:
start_time = seg.start_time_ms start_time = int(seg.start_time_ms)
end_time = seg.end_time_ms end_time = int(seg.end_time_ms)
if ( if (
current_group current_group
and (start_time - prev_end > max_gap_ms and (start_time - prev_end > max_gap_ms
+1
View File
@@ -26,6 +26,7 @@ sageattention==1.0.6
scikit_learn==1.7.2 scikit_learn==1.7.2
scipy==1.15.3 scipy==1.15.3
six==1.17.0 six==1.17.0
setuptools==81.0.0
scikit_image==0.25.2 scikit_image==0.25.2
soundfile==0.13.1 soundfile==0.13.1
ToJyutping==3.2.0 ToJyutping==3.2.0
+41 -41
View File
@@ -56,7 +56,7 @@ class LlamaNARDecoderLayer(LlamaDecoderLayer):
config.hidden_size, eps=config.rms_norm_eps, dim_cond=config.hidden_size config.hidden_size, eps=config.rms_norm_eps, dim_cond=config.hidden_size
) )
# add `cond` in forward function # 修改点:添加 position_embeddings 参数和 **kwargs
def forward( def forward(
self, self,
hidden_states: torch.Tensor, hidden_states: torch.Tensor,
@@ -66,38 +66,39 @@ class LlamaNARDecoderLayer(LlamaDecoderLayer):
past_key_value: Optional[Tuple[torch.Tensor]] = None, past_key_value: Optional[Tuple[torch.Tensor]] = None,
output_attentions: Optional[bool] = False, output_attentions: Optional[bool] = False,
use_cache: Optional[bool] = False, use_cache: Optional[bool] = False,
position_embeddings: Optional[Tuple[torch.Tensor, torch.Tensor]] = None,
**kwargs,
) -> Tuple[ ) -> Tuple[
torch.FloatTensor, Optional[Tuple[torch.FloatTensor, torch.FloatTensor]] torch.FloatTensor, Optional[Tuple[torch.FloatTensor, torch.FloatTensor]]
]: ]:
"""
Args:
hidden_states (`torch.FloatTensor`): input to the layer of shape `(batch, seq_len, embed_dim)`
attention_mask (`torch.FloatTensor`, *optional*): attention mask of size
`(batch, 1, tgt_len, src_len)` where padding elements are indicated by very large negative values.
output_attentions (`bool`, *optional*):
Whether or not to return the attentions tensors of all attention layers. See `attentions` under
returned tensors for more detail.
use_cache (`bool`, *optional*):
If set to `True`, `past_key_values` key value states are returned and can be used to speed up decoding
(see `past_key_values`).
past_key_value (`Tuple(torch.FloatTensor)`, *optional*): cached past key and value projection states
"""
residual = hidden_states residual = hidden_states
hidden_states = self.input_layernorm( hidden_states = self.input_layernorm(
hidden_states, cond_embedding=cond_embedding hidden_states, cond_embedding=cond_embedding
) )
# Self Attention # [MODIFIED] 兼容新版 transformers 的返回值解包逻辑
hidden_states, self_attn_weights, present_key_value = self.self_attn( # 因为新版在 output_attentions=False 时可能只返回 (hidden_states, present_key_value)
attn_outputs = self.self_attn(
hidden_states=hidden_states, hidden_states=hidden_states,
attention_mask=attention_mask, attention_mask=attention_mask,
position_ids=position_ids, position_ids=position_ids,
past_key_value=past_key_value, past_key_value=past_key_value,
output_attentions=output_attentions, output_attentions=output_attentions,
use_cache=use_cache, use_cache=use_cache,
position_embeddings=position_embeddings,
) )
hidden_states = attn_outputs[0]
# 动态处理权重和 KV 缓存的解包
if output_attentions:
self_attn_weights = attn_outputs[1]
present_key_value = attn_outputs[2] if use_cache else None
else:
self_attn_weights = None
present_key_value = attn_outputs[1] if use_cache else None
hidden_states = residual + hidden_states hidden_states = residual + hidden_states
# Fully Connected # Fully Connected
@@ -129,7 +130,15 @@ class DiffLlama(LlamaModel):
dropout=0.1, dropout=0.1,
ffn_dropout=0.1, ffn_dropout=0.1,
attention_dropout=0.0, attention_dropout=0.0,
config=LlamaConfig(0, 256, 1024, 1, 1), config=LlamaConfig(
vocab_size=0,
hidden_size=256,
intermediate_size=1024,
num_hidden_layers=1,
num_attention_heads=4, # 确保维度对齐 (256/4=64)
num_key_value_heads=4,
attn_implementation="eager",
),
): ):
super().__init__(config) super().__init__(config)
@@ -293,6 +302,13 @@ class DiffLlama(LlamaModel):
else: else:
position_ids = position_ids.view(-1, seq_length).long() position_ids = position_ids.view(-1, seq_length).long()
# ==============================================================
# [NEW] 修复位置:为适配新版 transformers 生成 position_embeddings
# ==============================================================
# self.rotary_emb 继承自 LlamaModel
position_embeddings = self.rotary_emb(hidden_states if 'hidden_states' in locals() else inputs_embeds, position_ids)
# ==============================================================
# embed positions # embed positions
if attention_mask is None: if attention_mask is None:
attention_mask = torch.ones( attention_mask = torch.ones(
@@ -300,6 +316,9 @@ class DiffLlama(LlamaModel):
dtype=torch.bool, dtype=torch.bool,
device=inputs_embeds.device, device=inputs_embeds.device,
) )
# 处理 attention_mask 格式
# 注意:某些新版本 transformers 此处返回值结构有变,如报错请检查此函数
attention_mask = self._prepare_decoder_attention_mask( attention_mask = self._prepare_decoder_attention_mask(
attention_mask, attention_mask,
(batch_size, seq_length), (batch_size, seq_length),
@@ -329,23 +348,11 @@ class DiffLlama(LlamaModel):
) )
if self.gradient_checkpointing and self.training: if self.gradient_checkpointing and self.training:
# 原始代码此处有 NotImplementedError,如果需要开启梯度检查点,
# 也需要将 position_embeddings 传入 custom_forward
raise NotImplementedError raise NotImplementedError
def create_custom_forward(module):
def custom_forward(*inputs):
# None for past_key_value
return module(*inputs, output_attentions, None)
return custom_forward
layer_outputs = torch.utils.checkpoint.checkpoint(
create_custom_forward(decoder_layer),
hidden_states,
attention_mask,
position_ids,
None,
)
else: else:
# [MODIFIED] 在调用 decoder_layer 时传入 position_embeddings
layer_outputs = decoder_layer( layer_outputs = decoder_layer(
hidden_states, hidden_states,
attention_mask=attention_mask, attention_mask=attention_mask,
@@ -354,6 +361,7 @@ class DiffLlama(LlamaModel):
output_attentions=output_attentions, output_attentions=output_attentions,
use_cache=use_cache, use_cache=use_cache,
cond_embedding=diffusion_step, cond_embedding=diffusion_step,
position_embeddings=position_embeddings, # 新增参数
) )
hidden_states = layer_outputs[0] hidden_states = layer_outputs[0]
@@ -375,14 +383,6 @@ class DiffLlama(LlamaModel):
hidden_states = self.mel_out_mlp(hidden_states) hidden_states = self.mel_out_mlp(hidden_states)
# if not return_dict:
# return tuple(v for v in [hidden_states, next_cache, all_hidden_states, all_self_attns] if v is not None)
# return BaseModelOutputWithPast(
# last_hidden_state=hidden_states,
# past_key_values=next_cache,
# hidden_states=all_hidden_states,
# attentions=all_self_attns,
# )
if return_dict: if return_dict:
return { return {
"output": hidden_states, "output": hidden_states,
+7
View File
@@ -3,6 +3,7 @@ import random
import shutil import shutil
import sys import sys
import traceback import traceback
import gc
from datetime import datetime from datetime import datetime
from pathlib import Path from pathlib import Path
from typing import Literal, Tuple from typing import Literal, Tuple
@@ -312,6 +313,9 @@ class AppState:
max_merge_duration=max_merge_duration, max_merge_duration=max_merge_duration,
language=lyric_lang or "Mandarin", language=lyric_lang or "Mandarin",
) )
gc.collect()
if torch.cuda.is_available():
torch.cuda.empty_cache()
return True, f"preprocess {audio_path} done" return True, f"preprocess {audio_path} done"
except Exception as e: except Exception as e:
return False, f"preprocess failed: {e}" return False, f"preprocess failed: {e}"
@@ -346,6 +350,9 @@ class AppState:
args.use_fp16 = self.use_fp16 args.use_fp16 = self.use_fp16
try: try:
svs_process(args, self.svs_config, self.svs_model) svs_process(args, self.svs_config, self.svs_model)
gc.collect()
if torch.cuda.is_available():
torch.cuda.empty_cache()
generated = save_dir / "generated.wav" generated = save_dir / "generated.wav"
if not generated.exists(): if not generated.exists():
return False, f"inference finished but {generated} not found", None, prompt_meta_path, target_meta_path return False, f"inference finished but {generated} not found", None, prompt_meta_path, target_meta_path
+7 -1
View File
@@ -1,6 +1,7 @@
import random import random
import sys import sys
import traceback import traceback
import gc
from datetime import datetime from datetime import datetime
from pathlib import Path from pathlib import Path
from typing import Literal from typing import Literal
@@ -165,6 +166,9 @@ class AppState:
vocal_f0 = save_path / "vocal_f0.npy" vocal_f0 = save_path / "vocal_f0.npy"
if not vocal_wav.exists() or not vocal_f0.exists(): if not vocal_wav.exists() or not vocal_f0.exists():
return False, f"preprocess output missing: {vocal_wav} or {vocal_f0}", None, None return False, f"preprocess output missing: {vocal_wav} or {vocal_f0}", None, None
gc.collect()
if torch.cuda.is_available():
torch.cuda.empty_cache()
return True, "ok", vocal_wav, vocal_f0 return True, "ok", vocal_wav, vocal_f0
except Exception as e: except Exception as e:
return False, f"preprocess failed: {e}", None, None return False, f"preprocess failed: {e}", None, None
@@ -241,7 +245,9 @@ class AppState:
mixed_path = save_dir / "generated_mixed.wav" mixed_path = save_dir / "generated_mixed.wav"
sf.write(str(mixed_path), mixed, mix_sr) sf.write(str(mixed_path), mixed, mix_sr)
generated = mixed_path generated = mixed_path
gc.collect()
if torch.cuda.is_available():
torch.cuda.empty_cache()
return True, "svc inference done", generated return True, "svc inference done", generated
except Exception as e: except Exception as e:
return False, f"svc inference failed: {e}", None return False, f"svc inference failed: {e}", None