diff --git a/.gitattributes b/.gitattributes index a6344aac8c09253b3b630fb776ae94478aa0275b..b8be986cf6c758055dc80b542cced677a0b28c73 100644 --- a/.gitattributes +++ b/.gitattributes @@ -33,3 +33,27 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text *.zip filter=lfs diff=lfs merge=lfs -text *.zst filter=lfs diff=lfs merge=lfs -text *tfevents* filter=lfs diff=lfs merge=lfs -text +apps/gradio_app/assets/examples/f5_tts/1/infer_audio.wav filter=lfs diff=lfs merge=lfs -text +apps/gradio_app/assets/examples/f5_tts/3/refer_audio.mp3 filter=lfs diff=lfs merge=lfs -text +apps/gradio_app/assets/examples/f5_tts/4/refer_audio.mp3 filter=lfs diff=lfs merge=lfs -text +assets/examples/f5_tts/1/infer_audio.wav filter=lfs diff=lfs merge=lfs -text +assets/examples/f5_tts/3/refer_audio.mp3 filter=lfs diff=lfs merge=lfs -text +assets/examples/f5_tts/4/refer_audio.mp3 filter=lfs diff=lfs merge=lfs -text +src/f5_tts/src/f5_tts/infer/examples/basic/basic_ref_en.wav filter=lfs diff=lfs merge=lfs -text +src/f5_tts/src/f5_tts/infer/examples/basic/basic_ref_zh.wav filter=lfs diff=lfs merge=lfs -text +src/f5_tts/src/f5_tts/infer/examples/multi/country.flac filter=lfs diff=lfs merge=lfs -text +src/f5_tts/src/f5_tts/infer/examples/multi/main.flac filter=lfs diff=lfs merge=lfs -text +src/f5_tts/src/f5_tts/infer/examples/multi/town.flac filter=lfs diff=lfs merge=lfs -text +src/third_party/BigVGAN/demo/examples/dance_24k.wav filter=lfs diff=lfs merge=lfs -text +src/third_party/BigVGAN/demo/examples/hifitts_44k.wav filter=lfs diff=lfs merge=lfs -text +src/third_party/BigVGAN/demo/examples/jensen_24k.wav filter=lfs diff=lfs merge=lfs -text +src/third_party/BigVGAN/demo/examples/libritts_24k.wav filter=lfs diff=lfs merge=lfs -text +src/third_party/BigVGAN/demo/examples/megalovania_24k.wav filter=lfs diff=lfs merge=lfs -text +src/third_party/BigVGAN/demo/examples/musdbhq_44k.wav filter=lfs diff=lfs merge=lfs -text +src/third_party/BigVGAN/demo/examples/musiccaps1_44k.wav filter=lfs diff=lfs merge=lfs -text +src/third_party/BigVGAN/demo/examples/musiccaps2_44k.wav filter=lfs diff=lfs merge=lfs -text +src/third_party/BigVGAN/demo/examples/queen_24k.wav filter=lfs diff=lfs merge=lfs -text +src/third_party/BigVGAN/filelists/LibriTTS/train-full.txt filter=lfs diff=lfs merge=lfs -text +tests/test_data/1/infer_audio.wav filter=lfs diff=lfs merge=lfs -text +tests/test_data/3/refer_audio.mp3 filter=lfs diff=lfs merge=lfs -text +tests/test_data/4/refer_audio.mp3 filter=lfs diff=lfs merge=lfs -text diff --git a/apps/gradio_app.py b/apps/gradio_app.py new file mode 100644 index 0000000000000000000000000000000000000000..528bb3c381e537c46c1010ede37738077e224220 --- /dev/null +++ b/apps/gradio_app.py @@ -0,0 +1,149 @@ +import gradio as gr +from gradio_app.components import ( + get_files_in_ckpts, handle_file_upload, + run_tts_inference, + run_setup_script +) +from gradio_app.asr_utils import transcribe_audio +from pathlib import Path + +def create_gradio_app(): + """Create Gradio interface for F5-TTS inference with Whisper ASR.""" + # Run setup script to ensure dependencies are installed + run_setup_script() + + # Function to update reference text based on audio file and Whisper checkbox + def update_ref_text(audio_file_path, use_whisper): + if use_whisper and audio_file_path: + return transcribe_audio(audio_file_path) + return gr.update() + + def toggle_model_inputs(use_upload): + return ( + gr.update(visible=not use_upload), + gr.update(visible=not use_upload), + gr.update(visible=not use_upload), + gr.update(visible=use_upload), + gr.update(visible=use_upload), + gr.update(visible=use_upload) + ) + + def load_example(ref_audio_path, ref_text, inf_text): + """Load example inputs and retrieve corresponding infer_audio for output.""" + # Find the matching example folder to get infer_audio + example_dirs = [ + Path("apps/gradio_app/assets/examples/f5_tts/1"), + Path("apps/gradio_app/assets/examples/f5_tts/2"), + Path("apps/gradio_app/assets/examples/f5_tts/3"), + Path("apps/gradio_app/assets/examples/f5_tts/4") + ] + inf_audio_path = None + for dir_path in example_dirs: + if dir_path.exists(): + ref_audio = next((f for f in dir_path.glob("refer_audio.*") if f.suffix in [".mp3", ".wav"]), None) + if ref_audio and str(ref_audio) == ref_audio_path: + inf_audio = next((f for f in dir_path.glob("infer_audio.*") if f.suffix in [".mp3", ".wav"]), None) + inf_audio_path = str(inf_audio) if inf_audio else None + break + + return ref_audio_path, ref_text, inf_text, inf_audio_path + + # Prepare examples for gr.Examples (exclude infer_audio from table) + example_dirs = [ + Path("apps/gradio_app/assets/examples/f5_tts/1"), + Path("apps/gradio_app/assets/examples/f5_tts/2"), + Path("apps/gradio_app/assets/examples/f5_tts/3"), + Path("apps/gradio_app/assets/examples/f5_tts/4") + ] + examples = [] + for dir_path in example_dirs: + if not dir_path.exists(): + continue + # Read text files + ref_text = (dir_path / "refer_text.txt").read_text(encoding="utf-8") if (dir_path / "refer_text.txt").exists() else "" + inf_text = (dir_path / "infer_text.txt").read_text(encoding="utf-8") if (dir_path / "infer_text.txt").exists() else "" + # Find audio files (mp3 or wav) + ref_audio = next((f for f in dir_path.glob("refer_audio.*") if f.suffix in [".mp3", ".wav"]), None) + examples.append([ + str(ref_audio) if ref_audio else None, + ref_text, + inf_text + ]) + + CSS = open("apps/gradio_app/static/styles.css", "r").read() + with gr.Blocks(css=CSS) as demo: + gr.Markdown("# F5-TTS Audio Generation") + gr.Markdown("Generate high-quality audio with a fine-tuned F5-TTS model. Upload reference audio, use Whisper ASR for transcription, enter text, adjust speed, and select or upload model files.") + + with gr.Row(): + with gr.Column(): + ref_audio = gr.Audio(label="Reference Audio", type="filepath") + with gr.Group(): + use_whisper = gr.Checkbox(label="Use Whisper ASR for Transcription", value=False) + ref_text = gr.Textbox( + label="Reference Text", + placeholder="e.g., Sau nhà Ngô, lần lượt các triều Đinh...", + lines=1 + ) + gen_text = gr.Textbox( + label="Generated Text", + placeholder="e.g., Nhà Tiền Lê, Lý và Trần đã chống trả...", + lines=1 + ) + generate_btn = gr.Button("Generate Audio") + with gr.Column(): + output_audio = gr.Audio(label="Generated Audio") + output_text = gr.Textbox(label="Status", interactive=False) + with gr.Group(): + speed = gr.Slider(0.5, 2.0, 1.0, step=0.1, label="Speed") + model_cfg = gr.Dropdown( + choices=get_files_in_ckpts([".yaml"]), + label="Model Config (*.yaml)", + value=get_files_in_ckpts([".yaml"])[0], + visible=True + ) + ckpt_file = gr.Dropdown( + choices=get_files_in_ckpts([".pt", ".safetensors"], include_subdirs=True), + label="Checkpoint File (*.pt or *.safetensors)", + value=get_files_in_ckpts([".pt", ".safetensors"], include_subdirs=True)[0], + visible=True + ) + vocab_file = gr.Dropdown( + choices=get_files_in_ckpts([".txt", ".safetensors"]), + label="Vocab File (*.txt or *.safetensors)", + value=get_files_in_ckpts([".txt", ".safetensors"])[0], + visible=True + ) + use_upload = gr.Checkbox(label="Upload Custom Model Files", value=False) + model_cfg_upload = gr.File(label="Model Config (*.yaml)", file_types=[".yaml"], visible=False) + ckpt_file_upload = gr.File(label="Checkpoint File (*.pt or *.safetensors)", file_types=[".pt", ".safetensors"], visible=False) + vocab_file_upload = gr.File(label="Vocab File (*.txt or *.safetensors)", file_types=[".txt", ".safetensors"], visible=False) + + # Add Examples component after both columns + gr.Examples( + examples=examples, + inputs=[ref_audio, ref_text, gen_text], + outputs=[ref_audio, ref_text, gen_text, output_audio], # Keep output_audio to display infer_audio + fn=load_example, + label="Example Inputs", + examples_per_page=4, + cache_examples=False + ) + + ref_audio.change(fn=update_ref_text, inputs=[ref_audio, use_whisper], outputs=ref_text) + use_whisper.change(fn=update_ref_text, inputs=[ref_audio, use_whisper], outputs=ref_text) + use_upload.change( + fn=toggle_model_inputs, + inputs=[use_upload], + outputs=[model_cfg, ckpt_file, vocab_file, model_cfg_upload, ckpt_file_upload, vocab_file_upload] + ) + generate_btn.click( + fn=run_tts_inference, + inputs=[ref_audio, ref_text, gen_text, speed, use_upload, model_cfg, ckpt_file, vocab_file], + outputs=[output_audio, output_text] + ) + return demo + +if __name__ == "__main__": + demo = create_gradio_app() + demo.launch(share=True) \ No newline at end of file diff --git a/apps/gradio_app/__init__.py b/apps/gradio_app/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/apps/gradio_app/asr_utils.py b/apps/gradio_app/asr_utils.py new file mode 100644 index 0000000000000000000000000000000000000000..00d45211ac36260ea6c257cfa4de4219b3b5a377 --- /dev/null +++ b/apps/gradio_app/asr_utils.py @@ -0,0 +1,16 @@ +from transformers import WhisperProcessor, WhisperForConditionalGeneration +import librosa + +def transcribe_audio(audio_file_path): + """Transcribe audio using PhoWhisper-tiny model.""" + try: + processor = WhisperProcessor.from_pretrained("vinai/PhoWhisper-tiny") + model = WhisperForConditionalGeneration.from_pretrained("vinai/PhoWhisper-tiny") + audio, sr = librosa.load(audio_file_path, sr=16000) + input_features = processor(audio, sampling_rate=16000, return_tensors="pt").input_features + forced_decoder_ids = processor.get_decoder_prompt_ids(language="vi", task="transcribe") + predicted_ids = model.generate(input_features, forced_decoder_ids=forced_decoder_ids) + transcription = processor.batch_decode(predicted_ids, skip_special_tokens=True) + return transcription[0] if transcription else "" + except Exception as e: + return f"Transcription error: {str(e)}" \ No newline at end of file diff --git a/apps/gradio_app/assets/examples/f5_tts/1/infer_audio.wav b/apps/gradio_app/assets/examples/f5_tts/1/infer_audio.wav new file mode 100644 index 0000000000000000000000000000000000000000..e6874d9400dbdb42744c0a53a1337c51ec9b0f42 --- /dev/null +++ b/apps/gradio_app/assets/examples/f5_tts/1/infer_audio.wav @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2924700ad369afabb4489eceec9c5e1e9c0fae90a3409f480678aba7a79a7378 +size 127020 diff --git a/apps/gradio_app/assets/examples/f5_tts/1/infer_text.txt b/apps/gradio_app/assets/examples/f5_tts/1/infer_text.txt new file mode 100644 index 0000000000000000000000000000000000000000..6d98f32bd7b9cf7a74ad956ac291e24bb596f57e --- /dev/null +++ b/apps/gradio_app/assets/examples/f5_tts/1/infer_text.txt @@ -0,0 +1 @@ +chào mọi người, mọi người khỏe không? \ No newline at end of file diff --git a/apps/gradio_app/assets/examples/f5_tts/1/refer_audio.mp3 b/apps/gradio_app/assets/examples/f5_tts/1/refer_audio.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..d2829af3bb370a21dc4051aefa9a82a43cd807a1 Binary files /dev/null and b/apps/gradio_app/assets/examples/f5_tts/1/refer_audio.mp3 differ diff --git a/apps/gradio_app/assets/examples/f5_tts/1/refer_text.txt b/apps/gradio_app/assets/examples/f5_tts/1/refer_text.txt new file mode 100644 index 0000000000000000000000000000000000000000..222bd929455a465b091a9be95d1667d5f8c61b06 --- /dev/null +++ b/apps/gradio_app/assets/examples/f5_tts/1/refer_text.txt @@ -0,0 +1 @@ +bạn và tôi đều như nhau nhé, rồi chúng ta đi đâu nè \ No newline at end of file diff --git a/apps/gradio_app/assets/examples/f5_tts/2/infer_audio.mp3 b/apps/gradio_app/assets/examples/f5_tts/2/infer_audio.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..a11fa1a8f650d704a420cfa47bf54d8fb48409d2 Binary files /dev/null and b/apps/gradio_app/assets/examples/f5_tts/2/infer_audio.mp3 differ diff --git a/apps/gradio_app/assets/examples/f5_tts/2/infer_text.txt b/apps/gradio_app/assets/examples/f5_tts/2/infer_text.txt new file mode 100644 index 0000000000000000000000000000000000000000..6c1829941f691439d5f665e6da59260c4f380a37 --- /dev/null +++ b/apps/gradio_app/assets/examples/f5_tts/2/infer_text.txt @@ -0,0 +1 @@ +Tôi rất khỏe,cảm ơn mọi người đã quan tâm. \ No newline at end of file diff --git a/apps/gradio_app/assets/examples/f5_tts/2/refer_audio.mp3 b/apps/gradio_app/assets/examples/f5_tts/2/refer_audio.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..5a0ae710f3bc9950cc1b480deafd630f87ec2569 Binary files /dev/null and b/apps/gradio_app/assets/examples/f5_tts/2/refer_audio.mp3 differ diff --git a/apps/gradio_app/assets/examples/f5_tts/2/refer_text.txt b/apps/gradio_app/assets/examples/f5_tts/2/refer_text.txt new file mode 100644 index 0000000000000000000000000000000000000000..f8f69be00b3ee5fdd61b4bc1532221172f451d1e --- /dev/null +++ b/apps/gradio_app/assets/examples/f5_tts/2/refer_text.txt @@ -0,0 +1 @@ +Chúng thường sống hòa bình với các loài động vật khác, kể cả những loài săn mồi. \ No newline at end of file diff --git a/apps/gradio_app/assets/examples/f5_tts/3/infer_audio.mp3 b/apps/gradio_app/assets/examples/f5_tts/3/infer_audio.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..ec075f57a4de3d70a14a6fc2c02ac39cf3f1464b Binary files /dev/null and b/apps/gradio_app/assets/examples/f5_tts/3/infer_audio.mp3 differ diff --git a/apps/gradio_app/assets/examples/f5_tts/3/infer_text.txt b/apps/gradio_app/assets/examples/f5_tts/3/infer_text.txt new file mode 100644 index 0000000000000000000000000000000000000000..f66539d36d5a961e75ae51283227824eda32888d --- /dev/null +++ b/apps/gradio_app/assets/examples/f5_tts/3/infer_text.txt @@ -0,0 +1 @@ +Nhà Tiền Lê, Lý và Trần đã chống trả các cuộc tấn công của nhà Tống và nhà Mông – Nguyên, đều thắng lợi và bảo vệ được Đại Việt. \ No newline at end of file diff --git a/apps/gradio_app/assets/examples/f5_tts/3/refer_audio.mp3 b/apps/gradio_app/assets/examples/f5_tts/3/refer_audio.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..02f6db866f8a12752a940df08eb2bae510252a9f --- /dev/null +++ b/apps/gradio_app/assets/examples/f5_tts/3/refer_audio.mp3 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cd15755a7704fd99247dfae618a4f8e9d9655af735def78e6fdec5467faca641 +size 183110 diff --git a/apps/gradio_app/assets/examples/f5_tts/3/refer_text.txt b/apps/gradio_app/assets/examples/f5_tts/3/refer_text.txt new file mode 100644 index 0000000000000000000000000000000000000000..c20226f73f3fe4ae3898079ea752cac5713e2531 --- /dev/null +++ b/apps/gradio_app/assets/examples/f5_tts/3/refer_text.txt @@ -0,0 +1 @@ +Sau nhà Ngô, lần lượt các triều Đinh, Tiền Lê, Lý và Trần tổ chức chính quyền tương tự các triều đại Trung Hoa, lấy Phật giáo làm tôn giáo chính của quốc gia và cho truyền bá cả Nho giáo và Đạo giáo. \ No newline at end of file diff --git a/apps/gradio_app/assets/examples/f5_tts/4/infer_audio.mp3 b/apps/gradio_app/assets/examples/f5_tts/4/infer_audio.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..df9455660b7ec0f0a430bab0e0ddcb2183e68513 Binary files /dev/null and b/apps/gradio_app/assets/examples/f5_tts/4/infer_audio.mp3 differ diff --git a/apps/gradio_app/assets/examples/f5_tts/4/infer_text.txt b/apps/gradio_app/assets/examples/f5_tts/4/infer_text.txt new file mode 100644 index 0000000000000000000000000000000000000000..1b420fadc9cffb4a88a1ee3d10e19fe8b8b34e5e --- /dev/null +++ b/apps/gradio_app/assets/examples/f5_tts/4/infer_text.txt @@ -0,0 +1 @@ +Người dân Đông Á cổ đại đã uống trà trong nhiều thế kỷ, thậm chí có thể là hàng thiên niên kỷ , trước khi sử dụng nó như một thức uống. \ No newline at end of file diff --git a/apps/gradio_app/assets/examples/f5_tts/4/refer_audio.mp3 b/apps/gradio_app/assets/examples/f5_tts/4/refer_audio.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..f7610d4bb88eda8ffa7be4f3350f70b5d32514b9 --- /dev/null +++ b/apps/gradio_app/assets/examples/f5_tts/4/refer_audio.mp3 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9ea81c8700f5ff2e6497c9beaa942b5ed107e03ae468472d78a4c8c80e3b63af +size 138388 diff --git a/apps/gradio_app/assets/examples/f5_tts/4/refer_text.txt b/apps/gradio_app/assets/examples/f5_tts/4/refer_text.txt new file mode 100644 index 0000000000000000000000000000000000000000..ed02670351ce15acdca6a759bb20ef2a9e24a1d5 --- /dev/null +++ b/apps/gradio_app/assets/examples/f5_tts/4/refer_text.txt @@ -0,0 +1 @@ +Cấu trúc sừng và mào là phổ biến ở tất cả các nhóm khủng long, và vài nhóm thậm chí còn phát triển các biến đổi bộ xương như giáp mô hoặc gai. \ No newline at end of file diff --git a/apps/gradio_app/components.py b/apps/gradio_app/components.py new file mode 100644 index 0000000000000000000000000000000000000000..f554c009dc8474ee7bde6545acab416ed630a0fe --- /dev/null +++ b/apps/gradio_app/components.py @@ -0,0 +1,90 @@ +import os +import subprocess +import uuid +from pathlib import Path +import shutil + +def run_setup_script(): + setup_script = os.path.join(os.path.dirname(__file__), "setup_scripts.py") + try: + result = subprocess.run(["python", setup_script], capture_output=True, text=True, check=True) + return result.stdout + except subprocess.CalledProcessError as e: + return f"Setup script failed: {e.stderr}" + + +def get_files_in_ckpts(extensions, include_subdirs=False): + """List files in ckpts directory with specified extensions, optionally including subdirectories.""" + ckpts_dir = Path("ckpts") + if not ckpts_dir.exists(): + return ["No files found"] + files = [] + for ext in extensions: + if include_subdirs: + files.extend([str(f) for f in ckpts_dir.glob(f"**/*{ext}")]) + else: + files.extend([str(f) for f in ckpts_dir.glob(f"*{ext}")]) + return files if files else ["No files found"] + +def handle_file_upload(file_obj, allowed_extensions): + """Copy uploaded file to a permanent location and validate extension.""" + if not file_obj: + return None, "No file uploaded." + try: + file_ext = os.path.splitext(file_obj.name)[1].lower() + if file_ext not in allowed_extensions: + return None, f"Invalid file extension. Allowed: {', '.join(allowed_extensions)}" + upload_dir = Path("uploads") + upload_dir.mkdir(exist_ok=True) + file_name = f"upload_{str(uuid.uuid4())[:8]}{file_ext}" + dest_path = upload_dir / file_name + shutil.copyfile(file_obj.name, dest_path) + return str(dest_path), None + except Exception as e: + return None, f"File upload error: {str(e)}" + +def run_tts_inference(ref_audio, ref_text, gen_text, speed, use_upload, model_cfg, ckpt_file, vocab_file): + """Run F5-TTS inference with selected or uploaded model files.""" + if use_upload: + model_cfg_path, model_cfg_error = handle_file_upload(model_cfg, [".yaml"]) + ckpt_file_path, ckpt_file_error = handle_file_upload(ckpt_file, [".pt", ".safetensors"]) + vocab_file_path, vocab_file_error = handle_file_upload(vocab_file, [".txt", ".safetensors"]) + if model_cfg_error or ckpt_file_error or vocab_file_error: + return None, model_cfg_error or ckpt_file_error or vocab_file_error + if not (model_cfg_path and ckpt_file_path and vocab_file_path): + return None, "Please upload all model files (model_cfg, ckpt_file, vocab_file)." + config = {"model_cfg": model_cfg_path, "ckpt_file": ckpt_file_path, "vocab_file": vocab_file_path} + else: + if any(f == "No files found" for f in [model_cfg, ckpt_file, vocab_file]): + return None, "No valid model files found in ckpts. Upload custom files or add files to ckpts." + config = {"model_cfg": model_cfg, "ckpt_file": ckpt_file, "vocab_file": vocab_file} + + if not ref_audio: + return None, "Reference audio is required." + + output_dir = "apps/gradio_app/temp_data" + os.makedirs(output_dir, exist_ok=True) + output_file = f"infer_audio_{str(uuid.uuid4())[:8]}.mp3" + output_path = os.path.join(output_dir, output_file) + + try: + command = [ + "python", "src/f5_tts/infer/infer_cli.py", + "--model_cfg", config["model_cfg"], + "--ckpt_file", config["ckpt_file"], + "--vocab_file", config["vocab_file"], + "--ref_audio", ref_audio, + "--ref_text", ref_text, + "--gen_text", gen_text, + "--speed", str(speed), + "--output_dir", output_dir, + "--output_file", output_file + ] + result = subprocess.run(command, capture_output=True, text=True) + if result.returncode != 0: + return None, f"Inference error: {result.stderr}" + if not os.path.exists(output_path): + return None, f"Output audio not found at {output_path}" + return output_path, "Audio generated successfully!" + except Exception as e: + return None, f"Inference error: {str(e)}" \ No newline at end of file diff --git a/apps/gradio_app/setup_scripts.py b/apps/gradio_app/setup_scripts.py new file mode 100644 index 0000000000000000000000000000000000000000..c0693a88b118ae5cc8e5e627cd2c30b6bfc0efe2 --- /dev/null +++ b/apps/gradio_app/setup_scripts.py @@ -0,0 +1,61 @@ +import subprocess +import sys +import os + +def run_script(script_path, args=None): + """ + Run a Python script using subprocess with optional arguments and handle errors. + Returns True if successful, False otherwise. + """ + try: + command = [sys.executable, script_path] + if args: + command.extend(args) + result = subprocess.run( + command, + check=True, + text=True, + capture_output=True + ) + print(f"Successfully executed {script_path}") + print(result.stdout) + return True + except subprocess.CalledProcessError as e: + print(f"Error executing {script_path}:") + print(e.stderr) + return False + except FileNotFoundError: + print(f"Script not found: {script_path}") + return False + +def main(): + """ + Main function to execute setup_third_party.py and download_ckpts.py in sequence. + """ + scripts_dir = "scripts" + scripts = [ + { + "path": os.path.join(scripts_dir, "setup_third_party.py"), + "args": None + }, + { + "path": os.path.join(scripts_dir, "download_ckpts.py"), + "args": [ + "--repo_id", "danhtran2mind/Vi-F5-TTS", + "--local_dir", "./ckpts", + "--pruning_model" + ] + } + ] + + for script in scripts: + script_path = script["path"] + args = script["args"] + print(f"Start running {script_path} {' '.join(args) if args else ''}\n") + if not run_script(script_path, args): + print(f"Stopping execution due to error in {script_path}") + sys.exit(1) + print(f"Completed {script_path}\n") + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/apps/gradio_app/static/scripts.js b/apps/gradio_app/static/scripts.js new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/apps/gradio_app/static/styles.css b/apps/gradio_app/static/styles.css new file mode 100644 index 0000000000000000000000000000000000000000..562018ba2d08b09b1a203a02d2d461077ba04f58 --- /dev/null +++ b/apps/gradio_app/static/styles.css @@ -0,0 +1,100 @@ +/* General body styling */ +.gradio-container { + background: linear-gradient(180deg, #f9fafb, #f1efef); + font-family: 'Quicksand', ui-sans-serif, sans-serif; + color: #6b46c1; /* Purple-800 for text (neutral hue) */ + font-size: 16px; /* Medium text size */ + font-weight: 400; +} + +/* Dark mode background */ +@media (prefers-color-scheme: dark) { + .gradio-container { + background: linear-gradient(180deg, #1f2937, #111827); + color: #d6bcfa; /* Lighter purple for dark mode */ + } +} + +/* Block styling (containers for components) */ +.block { + border: 1px solid #e9d8fd; /* Purple-200 for borders */ + border-radius: 8px; /* Medium radius */ + box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05); /* Small shadow */ + padding: 16px; /* Medium spacing */ + background: #f1efef; +} + +/* Input fields */ +input[type="text"], textarea { + background: #faf5ff; /* Purple-50 for input background */ + border: 1px solid #e9d8fd; /* Purple-200 for borders */ + border-radius: 8px; + padding: 8px; + font-family: 'Quicksand', ui-sans-serif, sans-serif; + font-size: 16px; + color: #6b46c1; + box-shadow: none; +} +input[type="text"]:focus, textarea:focus { + outline: none; + box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1); /* Small shadow on focus */ + border-color: #48bb78; /* Green-400 for focus */ +} + +/* Primary button */ +button.primary { + background: #48bb78; /* Green-400 */ + color: #f1efef; + border: none; + border-radius: 8px; + padding: 8px 16px; + font-family: 'Quicksand', ui-sans-serif, sans-serif; + font-size: 16px; + font-weight: 500; + box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05); + cursor: pointer; +} +button.primary:hover { + background: #ed8936; /* Orange-400 for hover */ + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); /* Medium shadow on hover */ +} + +/* Secondary button */ +button.secondary { + color: #48bb78; /* Green-400 for text */ + border: 1px solid #48bb78; /* Green-400 for border */ + border-radius: 8px; + padding: 8px 16px; + font-family: 'Quicksand', ui-sans-serif, sans-serif; + font-size: 16px; + font-weight: 500; + box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05); + cursor: pointer; +} +button.secondary:hover { + background: #ed8936; /* Orange-400 for hover */ + color: #48bb78; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); +} + +/* Slider styling */ +input[type="range"] { + accent-color: #ed8936; /* Orange-400 for slider */ +} +@media (prefers-color-scheme: dark) { + input[type="range"] { + accent-color: #f6ad55; /* Orange-600 for dark mode */ + } +} + +/* Markdown headers */ +h2 { + font-weight: 500; + color: #6b46c1; /* Purple-800 */ + margin-bottom: 16px; +} + +/* Code or monospace elements */ +code, pre { + font-family: 'IBM Plex Mono', ui-monospace, monospace; +} \ No newline at end of file diff --git a/apps/old-gradio_app.py b/apps/old-gradio_app.py new file mode 100644 index 0000000000000000000000000000000000000000..ba40a70e93e3269ac87944d087654caa082201bf --- /dev/null +++ b/apps/old-gradio_app.py @@ -0,0 +1,140 @@ +import gradio as gr +import os +import subprocess +import tempfile +from pathlib import Path +from transformers import WhisperProcessor, WhisperForConditionalGeneration +import librosa + +def transcribe_audio(audio_file_path): + """Transcribe audio using PhoWhisper-tiny model.""" + try: + processor = WhisperProcessor.from_pretrained("vinai/PhoWhisper-tiny") + model = WhisperForConditionalGeneration.from_pretrained("vinai/PhoWhisper-tiny") + audio, sr = librosa.load(audio_file_path, sr=16000) + input_features = processor(audio, sampling_rate=16000, return_tensors="pt").input_features + forced_decoder_ids = processor.get_decoder_prompt_ids(language="vi", task="transcribe") + predicted_ids = model.generate(input_features, forced_decoder_ids=forced_decoder_ids) + transcription = processor.batch_decode(predicted_ids, skip_special_tokens=True) + return transcription[0] if transcription else "" + except Exception as e: + return f"Error during transcription: {str(e)}" + +def run_tts_inference(ref_audio, ref_text, gen_text, speed, model_option): + """ + Run the F5-TTS inference script with provided inputs and return the output audio path. + """ + model_configs = { + "Vietnamese Fine-Tuned": { + "model_cfg": "ckpts/vi-fine-tuned-f5-tts.yaml", + "ckpt_file": "ckpts/Vi_F5_TTS_ckpts/pruning_model.pt", + "vocab_file": "ckpts/vocab.txt" + }, + } + + if model_option not in model_configs: + return None, f"Invalid model option: {model_option}" + + config = model_configs[model_option] + + output_dir = "apps/gradio_app/temp_data" + os.makedirs(output_dir, exist_ok=True) + output_file = "infer_audio.mp3" + output_path = os.path.join(output_dir, output_file) + + if ref_audio: + temp_audio = ref_audio + else: + return None, "Reference audio is required" + + # with tempfile.NamedTemporaryFile(mode='w', suffix='.txt', delete=False) as temp_ref_text: + # temp_ref_text.write(ref_text or "") + # temp_ref_text_path = temp_ref_text.name + + # with tempfile.NamedTemporaryFile(mode='w', suffix='.txt', delete=False) as temp_gen_text: + # temp_gen_text.write(gen_text or "") + # temp_gen_text_path = temp_gen_text.name + + try: + command = [ + "python", "src/f5_tts/infer/infer_cli.py", + "--model_cfg", config["model_cfg"], + "--ckpt_file", config["ckpt_file"], + "--vocab_file", config["vocab_file"], + "--ref_audio", temp_audio, + "--ref_text", ref_text, + "--gen_text", gen_text, + "--speed", str(speed), + "--output_dir", output_dir, + "--output_file", output_file + ] + + result = subprocess.run(command, capture_output=True, text=True) + + if result.returncode != 0: + return None, f"Error running inference: {result.stderr}" + + if not os.path.exists(output_path): + return None, f"Output audio file not found at {output_path}" + + return output_path, "Audio generated successfully!" + + except Exception as e: + return None, f"Error during inference: {str(e)}" + + +def create_gradio_app(): + """ + Create and return a Gradio interface for the F5-TTS inference with optional Whisper ASR. + """ + def update_ref_text(audio_file_path, use_whisper): + """Conditionally transcribe audio based on Whisper checkbox.""" + if use_whisper and audio_file_path: + return transcribe_audio(audio_file_path) + return gr.update() # Keep current text if Whisper is disabled or no audio + + with gr.Blocks() as demo: + gr.Markdown("# F5-TTS Audio Generation App") + gr.Markdown("Generate audio using a fine-tuned F5-TTS model. Upload a reference audio, enable Whisper ASR for auto-transcription or manually enter reference text, provide generated text, and adjust the speed.") + + with gr.Row(): + with gr.Column(): + ref_audio = gr.Audio(label="Reference Audio", type="filepath") + use_whisper = gr.Checkbox(label="Use Whisper ASR for Reference Text", value=False) + ref_text = gr.Textbox(label="Reference Text", placeholder="e.g., Sau nhà Ngô, lần lượt các triều Đinh...") + gen_text = gr.Textbox(label="Generated Text", placeholder="e.g., Nhà Tiền Lê, Lý và Trần đã chống trả...") + speed = gr.Slider(0.5, 2.0, 1.0, step=0.1, label="Speed") + model_option = gr.Dropdown( + choices=["Vietnamese Fine-Tuned"], + label="Model Option", + value="Vietnamese Fine-Tuned" + ) + generate_btn = gr.Button("Generate Audio") + + with gr.Column(): + output_audio = gr.Audio(label="Generated Audio") + output_text = gr.Textbox(label="Status") + + # Update reference text when audio is uploaded or Whisper checkbox changes + ref_audio.change( + fn=update_ref_text, + inputs=[ref_audio, use_whisper], + outputs=ref_text + ) + use_whisper.change( + fn=update_ref_text, + inputs=[ref_audio, use_whisper], + outputs=ref_text + ) + + generate_btn.click( + fn=run_tts_inference, + inputs=[ref_audio, ref_text, gen_text, speed, model_option], + outputs=[output_audio, output_text] + ) + + return demo + +if __name__ == "__main__": + demo = create_gradio_app() + demo.launch(share=True) diff --git a/assets/.gitkeep b/assets/.gitkeep new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/assets/examples/f5_tts/1/infer_audio.wav b/assets/examples/f5_tts/1/infer_audio.wav new file mode 100644 index 0000000000000000000000000000000000000000..e6874d9400dbdb42744c0a53a1337c51ec9b0f42 --- /dev/null +++ b/assets/examples/f5_tts/1/infer_audio.wav @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2924700ad369afabb4489eceec9c5e1e9c0fae90a3409f480678aba7a79a7378 +size 127020 diff --git a/assets/examples/f5_tts/1/infer_text.txt b/assets/examples/f5_tts/1/infer_text.txt new file mode 100644 index 0000000000000000000000000000000000000000..6d98f32bd7b9cf7a74ad956ac291e24bb596f57e --- /dev/null +++ b/assets/examples/f5_tts/1/infer_text.txt @@ -0,0 +1 @@ +chào mọi người, mọi người khỏe không? \ No newline at end of file diff --git a/assets/examples/f5_tts/1/refer_audio.mp3 b/assets/examples/f5_tts/1/refer_audio.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..d2829af3bb370a21dc4051aefa9a82a43cd807a1 Binary files /dev/null and b/assets/examples/f5_tts/1/refer_audio.mp3 differ diff --git a/assets/examples/f5_tts/1/refer_text.txt b/assets/examples/f5_tts/1/refer_text.txt new file mode 100644 index 0000000000000000000000000000000000000000..222bd929455a465b091a9be95d1667d5f8c61b06 --- /dev/null +++ b/assets/examples/f5_tts/1/refer_text.txt @@ -0,0 +1 @@ +bạn và tôi đều như nhau nhé, rồi chúng ta đi đâu nè \ No newline at end of file diff --git a/assets/examples/f5_tts/2/infer_audio.mp3 b/assets/examples/f5_tts/2/infer_audio.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..a11fa1a8f650d704a420cfa47bf54d8fb48409d2 Binary files /dev/null and b/assets/examples/f5_tts/2/infer_audio.mp3 differ diff --git a/assets/examples/f5_tts/2/infer_text.txt b/assets/examples/f5_tts/2/infer_text.txt new file mode 100644 index 0000000000000000000000000000000000000000..6c1829941f691439d5f665e6da59260c4f380a37 --- /dev/null +++ b/assets/examples/f5_tts/2/infer_text.txt @@ -0,0 +1 @@ +Tôi rất khỏe,cảm ơn mọi người đã quan tâm. \ No newline at end of file diff --git a/assets/examples/f5_tts/2/refer_audio.mp3 b/assets/examples/f5_tts/2/refer_audio.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..5a0ae710f3bc9950cc1b480deafd630f87ec2569 Binary files /dev/null and b/assets/examples/f5_tts/2/refer_audio.mp3 differ diff --git a/assets/examples/f5_tts/2/refer_text.txt b/assets/examples/f5_tts/2/refer_text.txt new file mode 100644 index 0000000000000000000000000000000000000000..f8f69be00b3ee5fdd61b4bc1532221172f451d1e --- /dev/null +++ b/assets/examples/f5_tts/2/refer_text.txt @@ -0,0 +1 @@ +Chúng thường sống hòa bình với các loài động vật khác, kể cả những loài săn mồi. \ No newline at end of file diff --git a/assets/examples/f5_tts/3/infer_audio.mp3 b/assets/examples/f5_tts/3/infer_audio.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..ec075f57a4de3d70a14a6fc2c02ac39cf3f1464b Binary files /dev/null and b/assets/examples/f5_tts/3/infer_audio.mp3 differ diff --git a/assets/examples/f5_tts/3/infer_text.txt b/assets/examples/f5_tts/3/infer_text.txt new file mode 100644 index 0000000000000000000000000000000000000000..f66539d36d5a961e75ae51283227824eda32888d --- /dev/null +++ b/assets/examples/f5_tts/3/infer_text.txt @@ -0,0 +1 @@ +Nhà Tiền Lê, Lý và Trần đã chống trả các cuộc tấn công của nhà Tống và nhà Mông – Nguyên, đều thắng lợi và bảo vệ được Đại Việt. \ No newline at end of file diff --git a/assets/examples/f5_tts/3/refer_audio.mp3 b/assets/examples/f5_tts/3/refer_audio.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..02f6db866f8a12752a940df08eb2bae510252a9f --- /dev/null +++ b/assets/examples/f5_tts/3/refer_audio.mp3 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cd15755a7704fd99247dfae618a4f8e9d9655af735def78e6fdec5467faca641 +size 183110 diff --git a/assets/examples/f5_tts/3/refer_text.txt b/assets/examples/f5_tts/3/refer_text.txt new file mode 100644 index 0000000000000000000000000000000000000000..c20226f73f3fe4ae3898079ea752cac5713e2531 --- /dev/null +++ b/assets/examples/f5_tts/3/refer_text.txt @@ -0,0 +1 @@ +Sau nhà Ngô, lần lượt các triều Đinh, Tiền Lê, Lý và Trần tổ chức chính quyền tương tự các triều đại Trung Hoa, lấy Phật giáo làm tôn giáo chính của quốc gia và cho truyền bá cả Nho giáo và Đạo giáo. \ No newline at end of file diff --git a/assets/examples/f5_tts/4/infer_audio.mp3 b/assets/examples/f5_tts/4/infer_audio.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..df9455660b7ec0f0a430bab0e0ddcb2183e68513 Binary files /dev/null and b/assets/examples/f5_tts/4/infer_audio.mp3 differ diff --git a/assets/examples/f5_tts/4/infer_text.txt b/assets/examples/f5_tts/4/infer_text.txt new file mode 100644 index 0000000000000000000000000000000000000000..1b420fadc9cffb4a88a1ee3d10e19fe8b8b34e5e --- /dev/null +++ b/assets/examples/f5_tts/4/infer_text.txt @@ -0,0 +1 @@ +Người dân Đông Á cổ đại đã uống trà trong nhiều thế kỷ, thậm chí có thể là hàng thiên niên kỷ , trước khi sử dụng nó như một thức uống. \ No newline at end of file diff --git a/assets/examples/f5_tts/4/refer_audio.mp3 b/assets/examples/f5_tts/4/refer_audio.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..f7610d4bb88eda8ffa7be4f3350f70b5d32514b9 --- /dev/null +++ b/assets/examples/f5_tts/4/refer_audio.mp3 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9ea81c8700f5ff2e6497c9beaa942b5ed107e03ae468472d78a4c8c80e3b63af +size 138388 diff --git a/assets/examples/f5_tts/4/refer_text.txt b/assets/examples/f5_tts/4/refer_text.txt new file mode 100644 index 0000000000000000000000000000000000000000..ed02670351ce15acdca6a759bb20ef2a9e24a1d5 --- /dev/null +++ b/assets/examples/f5_tts/4/refer_text.txt @@ -0,0 +1 @@ +Cấu trúc sừng và mào là phổ biến ở tất cả các nhóm khủng long, và vài nhóm thậm chí còn phát triển các biến đổi bộ xương như giáp mô hoặc gai. \ No newline at end of file diff --git a/ckpts/.gitkeep b/ckpts/.gitkeep new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/configs/.gitkeep b/configs/.gitkeep new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/configs/vi-fine-tuned-f5-tts.yaml b/configs/vi-fine-tuned-f5-tts.yaml new file mode 100644 index 0000000000000000000000000000000000000000..84a5b12e4cfba6fab2d6c9204f547eab5199a496 --- /dev/null +++ b/configs/vi-fine-tuned-f5-tts.yaml @@ -0,0 +1,52 @@ +hydra: + run: + dir: ckpts/${model.name}_${model.mel_spec.mel_spec_type}_${model.tokenizer}_${datasets.name}/${now:%Y-%m-%d}/${now:%H-%M-%S} + +datasets: + name: vin100h-preprocessed-v2 # dataset name + batch_size_per_gpu: 3200 # 1 GPUs, 1 * 3200 = 3200 + batch_size_type: frame # frame | sample + max_samples: 64 # max sequences per batch if use frame-wise batch_size. we set 32 for small models, 64 for base models + num_workers: 4 + +optim: + epochs: 80 + learning_rate: 1e-5 + num_warmup_updates: 2761 # warmup updates + grad_accumulation_steps: 2 # note: updates = steps / grad_accumulation_steps + max_grad_norm: 1.0 # gradient clipping + bnb_optimizer: False # use bnb 8bit AdamW optimizer or not + +model: + name: vi_fine_tuned_t5_tts # model name + tokenizer: pinyin # tokenizer type + tokenizer_path: null # if 'custom' tokenizer, define the path want to use (should be vocab.txt) + backbone: DiT + arch: + dim: 1024 + depth: 22 + heads: 16 + ff_mult: 2 + text_dim: 512 + text_mask_padding: False + conv_layers: 4 + pe_attn_head: 1 + checkpoint_activations: False # recompute activations and save memory for extra compute + mel_spec: + target_sample_rate: 24000 + n_mel_channels: 100 + hop_length: 256 + win_length: 1024 + n_fft: 1024 + mel_spec_type: vocos # vocos | bigvgan + vocoder: + is_local: False # use local offline ckpt or not + local_path: null # local vocoder path + +ckpts: + logger: null # wandb | tensorboard | null + log_samples: True # infer random sample per save checkpoint. wip, normal to fail with extra long samples + save_per_updates: 4000 # save checkpoint per updates + keep_last_n_checkpoints: 1 # -1 to keep all, 0 to not save intermediate, > 0 to keep last N checkpoints + last_per_updates: 4000 # save last checkpoint per updates + save_dir: ckpts/${model.name}_${model.mel_spec.mel_spec_type}_${model.tokenizer}_${datasets.name} \ No newline at end of file diff --git a/data/.gitkeep b/data/.gitkeep new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/docs/.gitkeep b/docs/.gitkeep new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/docs/inference/inference_doc.md b/docs/inference/inference_doc.md new file mode 100644 index 0000000000000000000000000000000000000000..ccf60c27ebaeb037e8edb7b902dd3e10a9677e02 --- /dev/null +++ b/docs/inference/inference_doc.md @@ -0,0 +1,38 @@ +# Inference Arguments + +The following table describes the command-line arguments available for the `infer-cli.py` script, which is used for text-to-speech (TTS) inference with advanced batch processing capabilities. These arguments allow users to override settings defined in the configuration file (`basic.toml` by default). + +| Argument | Description | Type | Default Value | Notes | +|----------|-------------|------|---------------|-------| +| `-c`, `--config` | Path to the configuration file. | `str` | `f5_tts/infer/examples/basic/basic.toml` | Specifies the TOML configuration file to use. | +| `-m`, `--model` | Model name to use for inference. | `str` | `F5TTS_v1_Base` (from config) | Options: `F5TTS_v1_Base`, `F5TTS_Base`, `E2TTS_Base`, etc. | +| `-mc`, `--model_cfg` | Path to the model's YAML configuration file. | `str` | `configs/.yaml` (from config) | Defines model-specific settings. | +| `-p`, `--ckpt_file` | Path to the model checkpoint file (.pt). | `str` | (from config) | Leave blank to use default checkpoint. | +| `-v`, `--vocab_file` | Path to the vocabulary file (.txt). | `str` | (from config) | Leave blank to use default vocabulary. | +| `-r`, `--ref_audio` | Path to the reference audio file. | `str` | `infer/examples/basic/basic_ref_en.wav` (from config) | Used as a reference for voice synthesis. | +| `-s`, `--ref_text` | Transcript or subtitle for the reference audio. | `str` | `Some call me nature, others call me mother nature.` (from config) | Text corresponding to the reference audio. | +| `-t`, `--gen_text` | Text to synthesize into speech. | `str` | `Here we generate something just for test.` (from config) | Ignored if `--gen_file` is provided. | +| `-f`, `--gen_file` | Path to a file containing text to synthesize. | `str` | (from config) | Overrides `--gen_text` if specified. | +| `-o`, `--output_dir` | Path to the output directory. | `str` | `tests` (from config) | Directory where generated audio files are saved. | +| `-w`, `--output_file` | Name of the output audio file. | `str` | `infer_cli_.wav` (from config) | Timestamp format: `%Y%m%d_%H%M%S`. | +| `--save_chunk` | Save individual audio chunks during inference. | `bool` | `False` (from config) | If enabled, saves chunks to `/_chunks/`. | +| `--no_legacy_text` | Disable lossy ASCII transliteration for Unicode text in file names. | `bool` | `False` (from config) | If disabled, uses Unicode in file names; warns if used with `--save_chunk`. | +| `--remove_silence` | Remove long silences from the generated audio. | `bool` | `False` (from config) | Applies silence removal post-processing. | +| `--load_vocoder_from_local` | Load vocoder from a local directory. | `bool` | `False` (from config) | Uses `../checkpoints/vocos-mel-24khz` or similar if enabled. | +| `--vocoder_name` | Name of the vocoder to use. | `str` | (from config, defaults to `mel_spec_type`) | Options: `vocos`, `bigvgan`. | +| `--target_rms` | Target loudness normalization value for output speech. | `float` | (from config, defaults to `target_rms`) | Adjusts audio loudness. | +| `--cross_fade_duration` | Duration of cross-fade between audio segments (seconds). | `float` | (from config, defaults to `cross_fade_duration`) | Smooths transitions between segments. | +| `--nfe_step` | Number of function evaluation (denoising) steps. | `int` | (from config, defaults to `nfe_step`) | Controls inference quality. | +| `--cfg_strength` | Classifier-free guidance strength. | `float` | (from config, defaults to `cfg_strength`) | Influences generation quality. | +| `--sway_sampling_coef` | Sway sampling coefficient. | `float` | (from config, defaults to `sway_sampling_coef`) | Affects sampling behavior. | +| `--speed` | Speed of the generated audio. | `float` | (from config, defaults to `speed`) | Adjusts playback speed. | +| `--fix_duration` | Fixed total duration for reference and generated audio (seconds). | `float` | (from config, defaults to `fix_duration`) | Enforces a specific duration. | +| `--device` | Device to run inference on. | `str` | (from config, defaults to `device`) | E.g., `cpu`, `cuda`. | + +## Notes +- Arguments without default values in the script (e.g., `--model`, `--ref_audio`) inherit defaults from the configuration file. +- The `--no_legacy_text` flag is implemented as `store_false`, so enabling it sets `use_legacy_text` to `False`. +- If `--gen_file` is provided, it overrides `--gen_text`. +- The script supports multiple voices defined in the config file under the `voices` key, with a fallback to a `main` voice. +- The output audio is saved as a WAV file, and optional chunked audio segments can be saved if `--save_chunk` is enabled. +- The script uses `cached_path` for downloading model checkpoints from Hugging Face if no local checkpoint is specified. \ No newline at end of file diff --git a/docs/training/training_doc.md b/docs/training/training_doc.md new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/notebooks/1-vi-fine-tuned-t5-tts.ipynb b/notebooks/1-vi-fine-tuned-t5-tts.ipynb new file mode 100644 index 0000000000000000000000000000000000000000..7714882f181cd203bc38c0c28f9b12af2bdc3b0d --- /dev/null +++ b/notebooks/1-vi-fine-tuned-t5-tts.ipynb @@ -0,0 +1,952 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "_cell_guid": "b1076dfc-b9ad-4769-8c92-a6c4dae69d19", + "_uuid": "8f2839f25d086af736a60e9eeb907d3b93b6e0e5", + "execution": { + "iopub.execute_input": "2025-06-15T14:21:25.974502Z", + "iopub.status.busy": "2025-06-15T14:21:25.974227Z", + "iopub.status.idle": "2025-06-15T14:21:31.475226Z", + "shell.execute_reply": "2025-06-15T14:21:31.474663Z", + "shell.execute_reply.started": "2025-06-15T14:21:25.974478Z" + }, + "trusted": true + }, + "outputs": [], + "source": [ + "import os\n", + "os.system(\"pip install -q wget\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "execution": { + "iopub.execute_input": "2025-06-15T14:21:31.476734Z", + "iopub.status.busy": "2025-06-15T14:21:31.476449Z", + "iopub.status.idle": "2025-06-15T14:21:37.092039Z", + "shell.execute_reply": "2025-06-15T14:21:37.091491Z", + "shell.execute_reply.started": "2025-06-15T14:21:31.476715Z" + }, + "trusted": true + }, + "outputs": [], + "source": [ + "import wget\n", + "import tarfile\n", + "import torchaudio\n", + "import pandas as pd\n", + "from huggingface_hub import snapshot_download, login\n", + "login(\"\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "execution": { + "iopub.execute_input": "2025-06-15T14:21:37.092984Z", + "iopub.status.busy": "2025-06-15T14:21:37.092705Z", + "iopub.status.idle": "2025-06-15T14:21:37.096562Z", + "shell.execute_reply": "2025-06-15T14:21:37.096039Z", + "shell.execute_reply.started": "2025-06-15T14:21:37.092967Z" + }, + "trusted": true + }, + "outputs": [], + "source": [ + "os.chdir(\"/content\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "execution": { + "iopub.execute_input": "2025-06-15T13:59:06.772020Z", + "iopub.status.busy": "2025-06-15T13:59:06.771694Z", + "iopub.status.idle": "2025-06-15T14:00:28.043176Z", + "shell.execute_reply": "2025-06-15T14:00:28.041603Z", + "shell.execute_reply.started": "2025-06-15T13:59:06.771995Z" + }, + "trusted": true + }, + "outputs": [], + "source": [ + "from huggingface_hub import HfApi\n", + "from huggingface_hub import snapshot_download\n", + "import os\n", + "api = HfApi()\n", + "!git lfs install --force\n", + "\n", + "# Define the dataset name and local directory\n", + "\n", + "repo_id = \"heboya8/t5-tts-temp-model\"\n", + "save_path = \".\"\n", + "\n", + "# Create the directory if it doesn't exist\n", + "os.makedirs(save_path, exist_ok=True)\n", + "\n", + "# Download the dataset\n", + "snapshot_download(repo_id=repo_id, repo_type=\"model\", local_dir=save_path)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "execution": { + "iopub.execute_input": "2025-06-15T14:21:37.389642Z", + "iopub.status.busy": "2025-06-15T14:21:37.389399Z", + "iopub.status.idle": "2025-06-15T14:24:47.468892Z", + "shell.execute_reply": "2025-06-15T14:24:47.468139Z", + "shell.execute_reply.started": "2025-06-15T14:21:37.389623Z" + }, + "trusted": true + }, + "outputs": [], + "source": [ + "# Step 1: Set Up the Environment\n", + "os.system(\"pip install -e . >/dev/null 2>&1\")\n", + "os.system(\"pip install torch==2.4.0+cu124 torchaudio==2.4.0+cu124 torchvision==0.19.0+cu124 --extra-index-url https://download.pytorch.org/whl/cu124 >/dev/null 2>&1\")\n", + "os.system(\"pip install accelerate==0.33.0 tensorboard >/dev/null 2>&1\")\n", + "if not os.path.exists(\"F5-TTS\"):\n", + " os.system(\"git clone https://github.com/SWivid/F5-TTS.git\")\n", + "os.chdir(\"F5-TTS\")\n", + "os.system(\"pip install -e . >/dev/null 2>&1\")\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "execution": { + "iopub.execute_input": "2025-06-15T14:24:47.470454Z", + "iopub.status.busy": "2025-06-15T14:24:47.470177Z", + "iopub.status.idle": "2025-06-15T14:24:47.473922Z", + "shell.execute_reply": "2025-06-15T14:24:47.473261Z", + "shell.execute_reply.started": "2025-06-15T14:24:47.470429Z" + }, + "trusted": true + }, + "outputs": [], + "source": [ + "os.chdir(\"/content/F5-TTS\")\n", + " # os.chdir(\"F5-TTS-Vietnamese\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "execution": { + "iopub.execute_input": "2025-06-15T06:47:34.909957Z", + "iopub.status.busy": "2025-06-15T06:47:34.909372Z", + "iopub.status.idle": "2025-06-15T06:47:35.040348Z", + "shell.execute_reply": "2025-06-15T06:47:35.039424Z", + "shell.execute_reply.started": "2025-06-15T06:47:34.909927Z" + }, + "trusted": true + }, + "outputs": [], + "source": [ + "!pwd" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "execution": { + "iopub.execute_input": "2025-06-15T14:24:47.475053Z", + "iopub.status.busy": "2025-06-15T14:24:47.474827Z", + "iopub.status.idle": "2025-06-15T14:24:47.644337Z", + "shell.execute_reply": "2025-06-15T14:24:47.643562Z", + "shell.execute_reply.started": "2025-06-15T14:24:47.475031Z" + }, + "trusted": true + }, + "outputs": [], + "source": [ + "!mkdir ./ckpts/vin100h-preprocessed-v2\n", + "# !cp /kaggle/input/vi-fine-tuned-t5-tts/69/model_last.pt \\\n", + "# ./ckpts/vin100h-preprocessed-v2\n", + "# !cp -r /content/73/* ./ckpts/vin100h-preprocessed-v2" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "execution": { + "iopub.execute_input": "2025-06-15T14:24:47.646473Z", + "iopub.status.busy": "2025-06-15T14:24:47.646278Z", + "iopub.status.idle": "2025-06-15T14:25:20.275283Z", + "shell.execute_reply": "2025-06-15T14:25:20.274453Z", + "shell.execute_reply.started": "2025-06-15T14:24:47.646454Z" + }, + "trusted": true + }, + "outputs": [], + "source": [ + "# !cp -r /kaggle/input/vi-fine-tuned-t5-tts/7/* ./ckpts\n", + "!cp -r /kaggle/input/vi-fine-tuned-t5-tts/75/model_last.pt \\\n", + " ./ckpts/vin100h-preprocessed-v2" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "execution": { + "iopub.execute_input": "2025-06-15T14:25:20.276407Z", + "iopub.status.busy": "2025-06-15T14:25:20.276159Z", + "iopub.status.idle": "2025-06-15T14:25:20.413414Z", + "shell.execute_reply": "2025-06-15T14:25:20.412180Z", + "shell.execute_reply.started": "2025-06-15T14:25:20.276382Z" + }, + "trusted": true + }, + "outputs": [], + "source": [ + "!ls -a ./ckpts/vin100h-preprocessed-v2" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "execution": { + "iopub.execute_input": "2025-05-10T15:59:08.329794Z", + "iopub.status.busy": "2025-05-10T15:59:08.329442Z", + "iopub.status.idle": "2025-05-10T15:59:09.362207Z", + "shell.execute_reply": "2025-05-10T15:59:09.361253Z", + "shell.execute_reply.started": "2025-05-10T15:59:08.329757Z" + }, + "trusted": true + }, + "outputs": [], + "source": [ + "import json\n", + "import os\n", + "from pathlib import Path\n", + "import shutil\n", + "import torchaudio\n", + "from datasets import load_dataset\n", + "from datasets.arrow_writer import ArrowWriter\n", + "from tqdm import tqdm\n", + "import soundfile as sf\n", + "import csv\n", + "\n", + "def save_dataset_to_local_disk(output_dir=\"./data/vin100h-preprocessed-v2\",\n", + " base_model=\"htdung167/vin100h-preprocessed-v2\",\n", + " audio_header='audio',\n", + " text_header='transcription'):\n", + " \n", + " wavs_dir = os.path.join(output_dir, \"wavs\")\n", + " metadata_path = os.path.join(output_dir, \"metadata.csv\")\n", + " os.makedirs(wavs_dir, exist_ok=True)\n", + "\n", + " ds = load_dataset(base_model)['train']\n", + " metadata = []\n", + "\n", + " for idx, sample in tqdm(enumerate(ds), total=len(ds),\n", + " desc=\"Saving samples to directory\"):\n", + " audio_array = sample[audio_header]['array']\n", + " sampling_rate = sample[audio_header]['sampling_rate']\n", + " filename = f\"audio_{idx:06d}.wav\"\n", + " sf.write(os.path.join(wavs_dir, filename), audio_array, sampling_rate)\n", + " # metadata.append([f\"wavs/{filename}\", sample['preprocessed_sentence_v2']])\n", + " metadata.append([f\"wavs/{filename}\", sample[text_header]])\n", + " # metadata.append([f\"{filename}\", sample['transcription']])\n", + " \n", + " with open(metadata_path, 'w', newline='', encoding='utf-8') as f:\n", + " csv.writer(f, delimiter='|').writerows(metadata)\n", + "\n", + " print(f\"Dataset saved to {output_dir}\")\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "execution": { + "iopub.execute_input": "2025-05-10T15:59:10.399030Z", + "iopub.status.busy": "2025-05-10T15:59:10.397916Z", + "iopub.status.idle": "2025-05-10T16:10:46.269067Z", + "shell.execute_reply": "2025-05-10T16:10:46.267298Z", + "shell.execute_reply.started": "2025-05-10T15:59:10.398995Z" + }, + "trusted": true + }, + "outputs": [], + "source": [ + "output_dir = \"./data/vin100h-preprocessed-v2\"\n", + "tokenizer_type = \"pinyin\"\n", + "\n", + "save_dataset_to_local_disk(output_dir=output_dir,\n", + " base_model=\"htdung167/vin100h-preprocessed-v2\",\n", + " text_header=\"preprocessed_sentence_v2\"\n", + " )" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "_kg_hide-output": true, + "execution": { + "iopub.execute_input": "2025-05-10T16:10:46.273403Z", + "iopub.status.busy": "2025-05-10T16:10:46.272176Z", + "iopub.status.idle": "2025-05-10T17:15:19.405258Z", + "shell.execute_reply": "2025-05-10T17:15:19.402002Z", + "shell.execute_reply.started": "2025-05-10T16:10:46.273366Z" + }, + "trusted": true + }, + "outputs": [], + "source": [ + "!python ./src/f5_tts/train/datasets/prepare_csv_wavs.py \\\n", + " \"./data/vin100h-preprocessed-v2\" \\\n", + " \"./data/vin100h-preprocessed-v2_pinyin\" \\\n", + " --workers 4 # Sets the number of parallel processes for preprocessing." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "execution": { + "iopub.execute_input": "2025-06-15T14:25:20.414900Z", + "iopub.status.busy": "2025-06-15T14:25:20.414621Z", + "iopub.status.idle": "2025-06-15T14:25:21.649820Z", + "shell.execute_reply": "2025-06-15T14:25:21.648942Z", + "shell.execute_reply.started": "2025-06-15T14:25:20.414873Z" + }, + "trusted": true + }, + "outputs": [], + "source": [ + "%%writefile ./src/f5_tts/configs/vi-fine-tuned-t5-tts.yaml\n", + "hydra:\n", + " run:\n", + " dir: ckpts/${model.name}_${model.mel_spec.mel_spec_type}_${model.tokenizer}_${datasets.name}/${now:%Y-%m-%d}/${now:%H-%M-%S}\n", + "\n", + "datasets:\n", + " name: vin100h-preprocessed-v2 # dataset name\n", + " batch_size_per_gpu: 3200 # 1 GPUs, 1 * 3200 = 3200\n", + " batch_size_type: frame # frame | sample\n", + " max_samples: 64 # max sequences per batch if use frame-wise batch_size. we set 32 for small models, 64 for base models\n", + " num_workers: 4\n", + "\n", + "optim:\n", + " epochs: 10\n", + " learning_rate: 1e-5\n", + " num_warmup_updates: 2761 # warmup updates\n", + " grad_accumulation_steps: 2 # note: updates = steps / grad_accumulation_steps\n", + " max_grad_norm: 1.0 # gradient clipping\n", + " bnb_optimizer: False # use bnb 8bit AdamW optimizer or not\n", + "\n", + "model:\n", + " name: vi_fine_tuned_t5_tts # model name\n", + " tokenizer: pinyin # tokenizer type\n", + " tokenizer_path: null # if 'custom' tokenizer, define the path want to use (should be vocab.txt)\n", + " backbone: DiT\n", + " arch:\n", + " dim: 1024\n", + " depth: 22\n", + " heads: 16\n", + " ff_mult: 2\n", + " text_dim: 512\n", + " text_mask_padding: False\n", + " conv_layers: 4\n", + " pe_attn_head: 1\n", + " checkpoint_activations: False # recompute activations and save memory for extra compute\n", + " mel_spec:\n", + " target_sample_rate: 24000\n", + " n_mel_channels: 100\n", + " hop_length: 256\n", + " win_length: 1024\n", + " n_fft: 1024\n", + " mel_spec_type: vocos # vocos | bigvgan\n", + " vocoder:\n", + " is_local: False # use local offline ckpt or not\n", + " local_path: null # local vocoder path\n", + "\n", + "ckpts:\n", + " logger: null # wandb | tensorboard | null\n", + " log_samples: True # infer random sample per save checkpoint. wip, normal to fail with extra long samples\n", + " save_per_updates: 4000 # save checkpoint per updates\n", + " keep_last_n_checkpoints: 1 # -1 to keep all, 0 to not save intermediate, > 0 to keep last N checkpoints\n", + " last_per_updates: 4000 # save last checkpoint per updates\n", + " save_dir: ckpts/${model.name}_${model.mel_spec.mel_spec_type}_${model.tokenizer}_${datasets.name}" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "execution": { + "iopub.execute_input": "2025-06-15T14:25:21.651011Z", + "iopub.status.busy": "2025-06-15T14:25:21.650749Z", + "iopub.status.idle": "2025-06-15T14:25:22.958480Z", + "shell.execute_reply": "2025-06-15T14:25:22.957781Z", + "shell.execute_reply.started": "2025-06-15T14:25:21.650992Z" + }, + "trusted": true + }, + "outputs": [], + "source": [ + "!echo hello" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "execution": { + "iopub.execute_input": "2025-06-15T14:25:22.959726Z", + "iopub.status.busy": "2025-06-15T14:25:22.959476Z", + "iopub.status.idle": "2025-06-15T14:25:38.131765Z", + "shell.execute_reply": "2025-06-15T14:25:38.130931Z", + "shell.execute_reply.started": "2025-06-15T14:25:22.959692Z" + }, + "trusted": true + }, + "outputs": [], + "source": [ + "!accelerate config default" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "execution": { + "iopub.execute_input": "2025-06-15T14:28:31.671797Z", + "iopub.status.busy": "2025-06-15T14:28:31.671483Z", + "iopub.status.idle": "2025-06-15T14:28:31.803519Z", + "shell.execute_reply": "2025-06-15T14:28:31.802848Z", + "shell.execute_reply.started": "2025-06-15T14:28:31.671770Z" + }, + "trusted": true + }, + "outputs": [], + "source": [ + "!echo go" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "execution": { + "iopub.execute_input": "2025-06-15T14:28:31.804624Z", + "iopub.status.busy": "2025-06-15T14:28:31.804419Z", + "iopub.status.idle": "2025-06-15T17:59:02.693078Z", + "shell.execute_reply": "2025-06-15T17:59:02.692025Z", + "shell.execute_reply.started": "2025-06-15T14:28:31.804591Z" + }, + "trusted": true + }, + "outputs": [], + "source": [ + "# ************\n", + "!accelerate launch ./src/f5_tts/train/finetune_cli.py \\\n", + " --exp_name F5TTS_Base \\\n", + " --dataset_name vin100h-preprocessed-v2 \\\n", + " --finetune \\\n", + " --tokenizer pinyin \\\n", + " --learning_rate 1e-05 \\\n", + " --batch_size_type frame \\\n", + " --batch_size_per_gpu 3200 \\\n", + " --max_samples 64 \\\n", + " --grad_accumulation_steps 2 \\\n", + " --max_grad_norm 1 \\\n", + " --epochs 76 \\\n", + " --num_warmup_updates 2761 \\\n", + " --save_per_updates 4000 \\\n", + " --keep_last_n_checkpoints 1 \\\n", + " --last_per_updates 4000 \\\n", + " --log_samples \\\n", + " --pretrain ./ckpts/vin100h-preprocessed-v2/model_last.pt\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "execution": { + "iopub.execute_input": "2025-06-15T18:05:50.705629Z", + "iopub.status.busy": "2025-06-15T18:05:50.704903Z", + "iopub.status.idle": "2025-06-15T18:05:50.891227Z", + "shell.execute_reply": "2025-06-15T18:05:50.890434Z", + "shell.execute_reply.started": "2025-06-15T18:05:50.705578Z" + }, + "trusted": true + }, + "outputs": [], + "source": [ + "!echo abc" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Copy and save" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "execution": { + "iopub.execute_input": "2025-06-14T10:18:46.384990Z", + "iopub.status.busy": "2025-06-14T10:18:46.384685Z", + "iopub.status.idle": "2025-06-14T10:18:46.518166Z", + "shell.execute_reply": "2025-06-14T10:18:46.517174Z", + "shell.execute_reply.started": "2025-06-14T10:18:46.384965Z" + }, + "trusted": true + }, + "outputs": [], + "source": [ + "!rm -rf /kaggle/working/.cache" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "execution": { + "iopub.execute_input": "2025-06-07T16:58:20.250613Z", + "iopub.status.busy": "2025-06-07T16:58:20.250305Z", + "iopub.status.idle": "2025-06-07T16:58:20.446725Z", + "shell.execute_reply": "2025-06-07T16:58:20.445927Z", + "shell.execute_reply.started": "2025-06-07T16:58:20.250588Z" + }, + "trusted": true + }, + "outputs": [], + "source": [ + "!ls -a ckpts/vin100h-preprocessed-v2" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "execution": { + "iopub.execute_input": "2025-06-15T18:06:00.980687Z", + "iopub.status.busy": "2025-06-15T18:06:00.979884Z", + "iopub.status.idle": "2025-06-15T18:06:07.418545Z", + "shell.execute_reply": "2025-06-15T18:06:07.417240Z", + "shell.execute_reply.started": "2025-06-15T18:06:00.980649Z" + }, + "trusted": true + }, + "outputs": [], + "source": [ + "# *******************Importance\n", + "model_dir = \"/kaggle/working/76\"\n", + "os.makedirs(model_dir, exist_ok=True)\n", + "!cp -r ./ckpts/vin100h-preprocessed-v2/model_last.pt $model_dir" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "execution": { + "iopub.status.busy": "2025-06-14T10:34:21.049620Z", + "iopub.status.idle": "2025-06-14T10:34:21.049856Z", + "shell.execute_reply": "2025-06-14T10:34:21.049753Z", + "shell.execute_reply.started": "2025-06-14T10:34:21.049740Z" + }, + "trusted": true + }, + "outputs": [], + "source": [ + "# To temporary Model hub\n", + "from huggingface_hub import HfApi\n", + "from huggingface_hub import snapshot_download\n", + "# Initialize API\n", + "api = HfApi()\n", + "\n", + "# Upload the folder to the repository root\n", + "api.upload_large_folder(\n", + " folder_path=\"/kaggle/working\", # Local folder path\n", + " repo_id=\"heboya8/t5-tts-temp-model\",\n", + " repo_type=\"model\"\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Prune Checkpoint" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "execution": { + "iopub.execute_input": "2025-05-11T14:11:57.837831Z", + "iopub.status.busy": "2025-05-11T14:11:57.837476Z", + "iopub.status.idle": "2025-05-11T14:11:57.844498Z", + "shell.execute_reply": "2025-05-11T14:11:57.843701Z", + "shell.execute_reply.started": "2025-05-11T14:11:57.837803Z" + }, + "trusted": true + }, + "outputs": [], + "source": [ + "import torch\n", + "\n", + "def prune_checkpoint(checkpoint_path: str, new_checkpoint_path: str, save_ema: bool, safetensors: bool) -> str:\n", + " try:\n", + " checkpoint = torch.load(checkpoint_path, weights_only=True)\n", + " print(\"Original Checkpoint Keys:\", checkpoint.keys())\n", + "\n", + " to_retain = \"ema_model_state_dict\" if save_ema else \"model_state_dict\"\n", + " try:\n", + " model_state_dict_to_retain = checkpoint[to_retain]\n", + " except KeyError:\n", + " return f\"{to_retain} not found in the checkpoint.\"\n", + "\n", + " if safetensors:\n", + " new_checkpoint_path = new_checkpoint_path.replace(\".pt\", \".safetensors\")\n", + " save_file(model_state_dict_to_retain, new_checkpoint_path)\n", + " else:\n", + " new_checkpoint_path = new_checkpoint_path.replace(\".safetensors\", \".pt\")\n", + " new_checkpoint = {\"ema_model_state_dict\": model_state_dict_to_retain}\n", + " torch.save(new_checkpoint, new_checkpoint_path)\n", + "\n", + " return f\"New checkpoint saved at: {new_checkpoint_path}\"\n", + "\n", + " except Exception as e:\n", + " return f\"An error occurred: {e}\"" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "execution": { + "iopub.execute_input": "2025-05-11T14:22:24.624318Z", + "iopub.status.busy": "2025-05-11T14:22:24.623974Z", + "iopub.status.idle": "2025-05-11T14:22:30.316195Z", + "shell.execute_reply": "2025-05-11T14:22:30.315529Z", + "shell.execute_reply.started": "2025-05-11T14:22:24.624292Z" + }, + "trusted": true + }, + "outputs": [], + "source": [ + "# Prune a checkpoint after training resize model\n", + "result = prune_checkpoint(\n", + " checkpoint_path=\"/kaggle/working/F5-TTS/ckpts/vin100h-preprocessed-v2/model_last.pt\",\n", + " new_checkpoint_path=\"/root/.cache/abc.pt\",\n", + " save_ema=False,\n", + " safetensors=False\n", + ")\n", + "print(result)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Inference" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "execution": { + "iopub.execute_input": "2025-05-20T17:08:02.683953Z", + "iopub.status.busy": "2025-05-20T17:08:02.683595Z", + "iopub.status.idle": "2025-05-20T17:08:02.753448Z", + "shell.execute_reply": "2025-05-20T17:08:02.752714Z", + "shell.execute_reply.started": "2025-05-20T17:08:02.683922Z" + }, + "trusted": true + }, + "outputs": [], + "source": [ + "from IPython.display import Audio\n", + "\n", + "# Path to your audio file\n", + "audio_path = './data/vin100h-preprocessed-v2/wavs/audio_000010.wav'\n", + "\n", + "# Display and play the audio\n", + "Audio(audio_path)\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "execution": { + "iopub.execute_input": "2025-06-14T10:24:03.249295Z", + "iopub.status.busy": "2025-06-14T10:24:03.248968Z", + "iopub.status.idle": "2025-06-14T10:24:41.393133Z", + "shell.execute_reply": "2025-06-14T10:24:41.391987Z", + "shell.execute_reply.started": "2025-06-14T10:24:03.249273Z" + }, + "trusted": true + }, + "outputs": [], + "source": [ + "!python ./src/f5_tts/infer/infer_cli.py \\\n", + " --model \"vin100h-preprocessed-v2\" \\\n", + " --model_cfg \"./src/f5_tts/configs/F5TTS_Base.yaml\" \\\n", + " --ckpt_file \"./ckpts/vin100h-preprocessed-v2/model_last.pt\" \\\n", + " --vocab_file \"./data/vin100h-preprocessed-v2_pinyin/vocab.txt\" \\\n", + " --ref_audio \"./data/vin100h-preprocessed-v2/wavs/audio_000010.wav\" \\\n", + " --ref_text \"Về giá cả so với giá bán ngoài các siêu thị thì dâu trái ở đây rẻ hơn khá nhiều. Giả sử như bó rau ở siêu thị bán khoảng 2 đô la một bó thì ở đây chỉ có một đô la một bó. Có khi mua 50 bó được tặng thêm một bó nữa.\" \\\n", + " --gen_text \"Về giá cả so với giá bán ngoài các siêu thị\" \\\n", + " --output_dir \"/kaggle/working/\"\n", + " # --output_file \"/content/abc.wav\"" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "execution": { + "iopub.execute_input": "2025-06-14T10:24:41.395230Z", + "iopub.status.busy": "2025-06-14T10:24:41.394917Z", + "iopub.status.idle": "2025-06-14T10:24:41.404325Z", + "shell.execute_reply": "2025-06-14T10:24:41.403321Z", + "shell.execute_reply.started": "2025-06-14T10:24:41.395199Z" + }, + "trusted": true + }, + "outputs": [], + "source": [ + "from IPython.display import Audio\n", + "\n", + "# Path to your audio file\n", + "audio_path = '/kaggle/working/infer_cli_basic.wav'\n", + "\n", + "# Display and play the audio\n", + "Audio(audio_path)\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Download" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "execution": { + "iopub.execute_input": "2025-06-15T14:25:38.133173Z", + "iopub.status.busy": "2025-06-15T14:25:38.132898Z", + "iopub.status.idle": "2025-06-15T14:26:12.006111Z", + "shell.execute_reply": "2025-06-15T14:26:12.005444Z", + "shell.execute_reply.started": "2025-06-15T14:25:38.133137Z" + }, + "trusted": true + }, + "outputs": [], + "source": [ + "from huggingface_hub import HfApi\n", + "from huggingface_hub import snapshot_download\n", + "import os\n", + "api = HfApi()\n", + "!git lfs install --force\n", + "\n", + "# Define the dataset name and local directory\n", + "repo_id = \"heboya8/f5-tts-dataset\"\n", + "save_path = \"/root/.cache\"\n", + "\n", + "# Create the directory if it doesn't exist\n", + "os.makedirs(save_path, exist_ok=True)\n", + "\n", + "# Download the dataset\n", + "snapshot_download(repo_id=repo_id, repo_type=\"dataset\", local_dir=save_path)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "execution": { + "iopub.execute_input": "2025-06-15T14:26:12.009357Z", + "iopub.status.busy": "2025-06-15T14:26:12.009122Z", + "iopub.status.idle": "2025-06-15T14:28:31.670192Z", + "shell.execute_reply": "2025-06-15T14:28:31.669158Z", + "shell.execute_reply.started": "2025-06-15T14:26:12.009338Z" + }, + "trusted": true + }, + "outputs": [], + "source": [ + "!unzip -q -o /root/.cache/data_compress.zip -d \".\"" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Upload" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "execution": { + "iopub.execute_input": "2025-05-10T20:06:26.721683Z", + "iopub.status.busy": "2025-05-10T20:06:26.720825Z", + "iopub.status.idle": "2025-05-10T20:11:36.850624Z", + "shell.execute_reply": "2025-05-10T20:11:36.849599Z", + "shell.execute_reply.started": "2025-05-10T20:06:26.721632Z" + }, + "trusted": true + }, + "outputs": [], + "source": [ + "from huggingface_hub import HfApi\n", + "from huggingface_hub import snapshot_download\n", + "# Initialize API\n", + "api = HfApi()\n", + "\n", + "# Upload the folder to the repository root\n", + "api.upload_large_folder(\n", + " folder_path=\"/root/.cache/dataset\", # Local folder path\n", + " repo_id=\"heboya8/f5-tts-dataset\",\n", + " repo_type=\"dataset\",\n", + " # multi_commits=True, # Enable resumable uploads\n", + " # multi_commits_verbose=True # Show progress\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## /kaggle/working/F5-TTS/ckpts/vin100h-preprocessed-v2/model_last.ptDowload Dataset" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "execution": { + "iopub.execute_input": "2025-05-10T20:16:38.191744Z", + "iopub.status.busy": "2025-05-10T20:16:38.191338Z", + "iopub.status.idle": "2025-05-10T20:16:56.134770Z", + "shell.execute_reply": "2025-05-10T20:16:56.133810Z", + "shell.execute_reply.started": "2025-05-10T20:16:38.191712Z" + }, + "trusted": true + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "execution": { + "iopub.execute_input": "2025-05-10T20:19:28.100798Z", + "iopub.status.busy": "2025-05-10T20:19:28.099915Z", + "iopub.status.idle": "2025-05-10T20:19:28.249902Z", + "shell.execute_reply": "2025-05-10T20:19:28.248723Z", + "shell.execute_reply.started": "2025-05-10T20:19:28.100762Z" + }, + "trusted": true + }, + "outputs": [], + "source": [ + "!mkdir dataset" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "execution": { + "iopub.execute_input": "2025-05-10T20:20:05.322822Z", + "iopub.status.busy": "2025-05-10T20:20:05.322019Z", + "iopub.status.idle": "2025-05-10T20:20:05.567705Z", + "shell.execute_reply": "2025-05-10T20:20:05.566624Z", + "shell.execute_reply.started": "2025-05-10T20:20:05.322785Z" + }, + "trusted": true + }, + "outputs": [], + "source": [ + "!rm -rf d /root/.cache/dataset" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "execution": { + "iopub.execute_input": "2025-05-10T20:20:07.132689Z", + "iopub.status.busy": "2025-05-10T20:20:07.132287Z", + "iopub.status.idle": "2025-05-10T20:22:58.875583Z", + "shell.execute_reply": "2025-05-10T20:22:58.874368Z", + "shell.execute_reply.started": "2025-05-10T20:20:07.132656Z" + }, + "trusted": true + }, + "outputs": [], + "source": [ + "!unzip -q /kaggle/working/F5-TTS/~/.cache/data_compress.zip -d /root/.cache/dataset" + ] + } + ], + "metadata": { + "kaggle": { + "accelerator": "none", + "dataSources": [ + { + "sourceId": 245622735, + "sourceType": "kernelVersion" + } + ], + "dockerImageVersionId": 31012, + "isGpuEnabled": false, + "isInternetEnabled": true, + "language": "python", + "sourceType": "notebook" + }, + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.11" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/notebooks/2-vi-fine-tuned-t5-tts.ipynb b/notebooks/2-vi-fine-tuned-t5-tts.ipynb new file mode 100644 index 0000000000000000000000000000000000000000..79c47d203c38be9fa6d047251a2ea5363eff26a0 --- /dev/null +++ b/notebooks/2-vi-fine-tuned-t5-tts.ipynb @@ -0,0 +1,1338 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "_cell_guid": "b1076dfc-b9ad-4769-8c92-a6c4dae69d19", + "_uuid": "8f2839f25d086af736a60e9eeb907d3b93b6e0e5", + "execution": { + "iopub.execute_input": "2025-06-17T15:01:35.597327Z", + "iopub.status.busy": "2025-06-17T15:01:35.596909Z", + "iopub.status.idle": "2025-06-17T15:01:41.413712Z", + "shell.execute_reply": "2025-06-17T15:01:41.413097Z", + "shell.execute_reply.started": "2025-06-17T15:01:35.597299Z" + }, + "trusted": true + }, + "outputs": [ + { + "data": { + "text/plain": [ + "0" + ] + }, + "execution_count": 1, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "import os\n", + "os.system(\"pip install -q wget\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "execution": { + "iopub.execute_input": "2025-06-17T15:01:41.415249Z", + "iopub.status.busy": "2025-06-17T15:01:41.415003Z", + "iopub.status.idle": "2025-06-17T15:01:47.137659Z", + "shell.execute_reply": "2025-06-17T15:01:47.137095Z", + "shell.execute_reply.started": "2025-06-17T15:01:41.415231Z" + }, + "trusted": true + }, + "outputs": [], + "source": [ + "import wget\n", + "import tarfile\n", + "import torchaudio\n", + "import pandas as pd\n", + "from huggingface_hub import snapshot_download, login\n", + "login(\"\")" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": { + "execution": { + "iopub.execute_input": "2025-06-17T15:01:47.138696Z", + "iopub.status.busy": "2025-06-17T15:01:47.138320Z", + "iopub.status.idle": "2025-06-17T15:01:47.142640Z", + "shell.execute_reply": "2025-06-17T15:01:47.141872Z", + "shell.execute_reply.started": "2025-06-17T15:01:47.138677Z" + }, + "trusted": true + }, + "outputs": [], + "source": [ + "os.chdir(\"/content\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "trusted": true + }, + "outputs": [], + "source": [ + "from huggingface_hub import HfApi\n", + "from huggingface_hub import snapshot_download\n", + "import os\n", + "api = HfApi()\n", + "!git lfs install --force\n", + "\n", + "# Define the dataset name and local directory\n", + "\n", + "repo_id = \"heboya8/t5-tts-temp-model\"\n", + "save_path = \"/content\"\n", + "\n", + "# Create the directory if it doesn't exist\n", + "os.makedirs(save_path, exist_ok=True)\n", + "\n", + "# Download the dataset\n", + "snapshot_download(repo_id=repo_id, repo_type=\"model\", local_dir=save_path)" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": { + "execution": { + "iopub.execute_input": "2025-06-17T01:51:26.479981Z", + "iopub.status.busy": "2025-06-17T01:51:26.477420Z", + "iopub.status.idle": "2025-06-17T01:51:26.676233Z", + "shell.execute_reply": "2025-06-17T01:51:26.674985Z", + "shell.execute_reply.started": "2025-06-17T01:51:26.479923Z" + }, + "trusted": true + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + ". .. 71 73 75 78 .cache .config\t.gitattributes\tsample_data\n" + ] + } + ], + "source": [ + "!ls -a" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": { + "execution": { + "iopub.execute_input": "2025-06-17T15:01:47.144207Z", + "iopub.status.busy": "2025-06-17T15:01:47.143938Z", + "iopub.status.idle": "2025-06-17T15:05:03.276239Z", + "shell.execute_reply": "2025-06-17T15:05:03.275559Z", + "shell.execute_reply.started": "2025-06-17T15:01:47.144181Z" + }, + "trusted": true + }, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Cloning into 'F5-TTS'...\n" + ] + }, + { + "data": { + "text/plain": [ + "0" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Step 1: Set Up the Environment\n", + "os.system(\"pip install -e . >/dev/null 2>&1\")\n", + "os.system(\"pip install torch==2.4.0+cu124 torchaudio==2.4.0+cu124 torchvision==0.19.0+cu124 --extra-index-url https://download.pytorch.org/whl/cu124 >/dev/null 2>&1\")\n", + "os.system(\"pip install accelerate==0.33.0 tensorboard >/dev/null 2>&1\")\n", + "if not os.path.exists(\"F5-TTS\"):\n", + " # os.system(\"git clone https://github.com/SWivid/F5-TTS.git\")\n", + " os.system(\"git clone https://github.com/danhtran2mind/F5-TTS.git\")\n", + "os.chdir(\"F5-TTS\")\n", + "os.system(\"pip install -e . >/dev/null 2>&1\")\n" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": { + "execution": { + "iopub.execute_input": "2025-06-17T15:05:03.277361Z", + "iopub.status.busy": "2025-06-17T15:05:03.277007Z", + "iopub.status.idle": "2025-06-17T15:05:03.280866Z", + "shell.execute_reply": "2025-06-17T15:05:03.280113Z", + "shell.execute_reply.started": "2025-06-17T15:05:03.277341Z" + }, + "trusted": true + }, + "outputs": [], + "source": [ + "os.chdir(\"/content/F5-TTS\")\n", + "# os.chdir(\"F5-TTS-Vietnamese\")" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": { + "execution": { + "iopub.execute_input": "2025-06-17T15:05:03.283201Z", + "iopub.status.busy": "2025-06-17T15:05:03.282849Z", + "iopub.status.idle": "2025-06-17T15:05:03.431616Z", + "shell.execute_reply": "2025-06-17T15:05:03.430672Z", + "shell.execute_reply.started": "2025-06-17T15:05:03.283176Z" + }, + "trusted": true + }, + "outputs": [], + "source": [ + "!mkdir ./ckpts/vin100h-preprocessed-v2" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": { + "execution": { + "iopub.execute_input": "2025-05-29T14:55:41.394312Z", + "iopub.status.busy": "2025-05-29T14:55:41.394058Z", + "iopub.status.idle": "2025-05-29T14:56:35.002821Z", + "shell.execute_reply": "2025-05-29T14:56:35.001574Z", + "shell.execute_reply.started": "2025-05-29T14:55:41.394290Z" + }, + "trusted": true + }, + "outputs": [], + "source": [ + "# !cp -r /kaggle/input/vi-fine-tuned-t5-tts/29/model_last.pt ./ckpts/vin100h-preprocessed-v2" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "execution": { + "execution_failed": "2025-05-29T13:44:32.926Z" + }, + "trusted": true + }, + "outputs": [], + "source": [ + "!mkdir ./ckpts/vin100h-preprocessed-v2" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": { + "execution": { + "iopub.execute_input": "2025-06-17T15:05:03.433154Z", + "iopub.status.busy": "2025-06-17T15:05:03.432814Z", + "iopub.status.idle": "2025-06-17T15:05:53.201849Z", + "shell.execute_reply": "2025-06-17T15:05:53.200797Z", + "shell.execute_reply.started": "2025-06-17T15:05:03.433120Z" + }, + "trusted": true + }, + "outputs": [], + "source": [ + "!cp -r /kaggle/input/vi-fine-tuned-t5-tts/80/model_last.pt \\\n", + "./ckpts/vin100h-preprocessed-v2" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": { + "execution": { + "iopub.execute_input": "2025-06-17T15:05:53.203394Z", + "iopub.status.busy": "2025-06-17T15:05:53.203095Z", + "iopub.status.idle": "2025-06-17T15:05:53.337400Z", + "shell.execute_reply": "2025-06-17T15:05:53.336629Z", + "shell.execute_reply.started": "2025-06-17T15:05:53.203359Z" + }, + "trusted": true + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + ". .. model_last.pt\n" + ] + } + ], + "source": [ + "!ls -a ./ckpts/vin100h-preprocessed-v2" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": { + "execution": { + "iopub.execute_input": "2025-05-21T16:31:43.230974Z", + "iopub.status.busy": "2025-05-21T16:31:43.230651Z", + "iopub.status.idle": "2025-05-21T16:31:57.026928Z", + "shell.execute_reply": "2025-05-21T16:31:57.025871Z", + "shell.execute_reply.started": "2025-05-21T16:31:43.230950Z" + }, + "trusted": true + }, + "outputs": [], + "source": [ + "# !cp -r ./ckpts/vin100h-preprocessed-v2/model_last.pt /kaggle/working/" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": { + "execution": { + "iopub.execute_input": "2025-05-21T16:33:02.071467Z", + "iopub.status.busy": "2025-05-21T16:33:02.071064Z", + "iopub.status.idle": "2025-05-21T16:33:02.193401Z", + "shell.execute_reply": "2025-05-21T16:33:02.192650Z", + "shell.execute_reply.started": "2025-05-21T16:33:02.071435Z" + }, + "trusted": true + }, + "outputs": [], + "source": [ + "!mv /kaggle/working/model_last.pt /kaggle/working/12/model_last.pt" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "execution": { + "iopub.execute_input": "2025-05-10T15:59:08.329794Z", + "iopub.status.busy": "2025-05-10T15:59:08.329442Z", + "iopub.status.idle": "2025-05-10T15:59:09.362207Z", + "shell.execute_reply": "2025-05-10T15:59:09.361253Z", + "shell.execute_reply.started": "2025-05-10T15:59:08.329757Z" + }, + "trusted": true + }, + "outputs": [], + "source": [ + "import json\n", + "import os\n", + "from pathlib import Path\n", + "import shutil\n", + "import torchaudio\n", + "from datasets import load_dataset\n", + "from datasets.arrow_writer import ArrowWriter\n", + "from tqdm import tqdm\n", + "import soundfile as sf\n", + "import csv\n", + "\n", + "def save_dataset_to_local_disk(output_dir=\"./data/vin100h-preprocessed-v2\",\n", + " base_model=\"htdung167/vin100h-preprocessed-v2\",\n", + " audio_header='audio', text_header='transcription'):\n", + " \n", + " wavs_dir = os.path.join(output_dir, \"wavs\")\n", + " metadata_path = os.path.join(output_dir, \"metadata.csv\")\n", + " os.makedirs(wavs_dir, exist_ok=True)\n", + "\n", + " ds = load_dataset(base_model)['train']\n", + " metadata = []\n", + "\n", + " for idx, sample in tqdm(enumerate(ds), total=len(ds),\n", + " desc=\"Saving samples to directory\"):\n", + " audio_array = sample[audio_header]['array']\n", + " sampling_rate = sample[audio_header]['sampling_rate']\n", + " filename = f\"audio_{idx:06d}.wav\"\n", + " sf.write(os.path.join(wavs_dir, filename), audio_array, sampling_rate)\n", + " # metadata.append([f\"wavs/{filename}\", sample['preprocessed_sentence_v2']])\n", + " metadata.append([f\"wavs/{filename}\", sample[text_header]])\n", + " # metadata.append([f\"{filename}\", sample['transcription']])\n", + " \n", + " with open(metadata_path, 'w', newline='', encoding='utf-8') as f:\n", + " csv.writer(f, delimiter='|').writerows(metadata)\n", + "\n", + " print(f\"Dataset saved to {output_dir}\")\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "execution": { + "iopub.execute_input": "2025-05-10T15:59:10.399030Z", + "iopub.status.busy": "2025-05-10T15:59:10.397916Z", + "iopub.status.idle": "2025-05-10T16:10:46.269067Z", + "shell.execute_reply": "2025-05-10T16:10:46.267298Z", + "shell.execute_reply.started": "2025-05-10T15:59:10.398995Z" + }, + "trusted": true + }, + "outputs": [], + "source": [ + "output_dir = \"./data/vin100h-preprocessed-v2\"\n", + "tokenizer_type = \"pinyin\"\n", + "\n", + "save_dataset_to_local_disk(output_dir=output_dir,\n", + " base_model=\"htdung167/vin100h-preprocessed-v2\",\n", + " text_header=\"preprocessed_sentence_v2\"\n", + " )" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "execution": { + "iopub.execute_input": "2025-05-10T16:10:46.273403Z", + "iopub.status.busy": "2025-05-10T16:10:46.272176Z", + "iopub.status.idle": "2025-05-10T17:15:19.405258Z", + "shell.execute_reply": "2025-05-10T17:15:19.402002Z", + "shell.execute_reply.started": "2025-05-10T16:10:46.273366Z" + }, + "trusted": true + }, + "outputs": [], + "source": [ + "!python ./src/f5_tts/train/datasets/prepare_csv_wavs.py \\\n", + " \"./data/vin100h-preprocessed-v2\" \\\n", + " \"./data/vin100h-preprocessed-v2_pinyin\" \\\n", + " --workers 4 # Sets the number of parallel processes for preprocessing." + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "metadata": { + "execution": { + "iopub.execute_input": "2025-06-17T15:20:02.239561Z", + "iopub.status.busy": "2025-06-17T15:20:02.238766Z", + "iopub.status.idle": "2025-06-17T15:20:02.245371Z", + "shell.execute_reply": "2025-06-17T15:20:02.244794Z", + "shell.execute_reply.started": "2025-06-17T15:20:02.239531Z" + }, + "trusted": true + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Writing ./src/f5_tts/configs/vi-fine-tuned-f5-tts.yaml\n" + ] + } + ], + "source": [ + "%%writefile ./src/f5_tts/configs/vi-fine-tuned-f5-tts.yaml\n", + "hydra:\n", + " run:\n", + " dir: ckpts/${model.name}_${model.mel_spec.mel_spec_type}_${model.tokenizer}_${datasets.name}/${now:%Y-%m-%d}/${now:%H-%M-%S}\n", + "\n", + "datasets:\n", + " name: vin100h-preprocessed-v2 # dataset name\n", + " batch_size_per_gpu: 3200 # 1 GPUs, 1 * 3200 = 3200\n", + " batch_size_type: frame # frame | sample\n", + " max_samples: 64 # max sequences per batch if use frame-wise batch_size. we set 32 for small models, 64 for base models\n", + " num_workers: 4\n", + "\n", + "optim:\n", + " epochs: 80\n", + " learning_rate: 1e-5\n", + " num_warmup_updates: 2761 # warmup updates\n", + " grad_accumulation_steps: 2 # note: updates = steps / grad_accumulation_steps\n", + " max_grad_norm: 1.0 # gradient clipping\n", + " bnb_optimizer: False # use bnb 8bit AdamW optimizer or not\n", + "\n", + "model:\n", + " name: vi_fine_tuned_t5_tts # model name\n", + " tokenizer: pinyin # tokenizer type\n", + " tokenizer_path: null # if 'custom' tokenizer, define the path want to use (should be vocab.txt)\n", + " backbone: DiT\n", + " arch:\n", + " dim: 1024\n", + " depth: 22\n", + " heads: 16\n", + " ff_mult: 2\n", + " text_dim: 512\n", + " text_mask_padding: False\n", + " conv_layers: 4\n", + " pe_attn_head: 1\n", + " checkpoint_activations: False # recompute activations and save memory for extra compute\n", + " mel_spec:\n", + " target_sample_rate: 24000\n", + " n_mel_channels: 100\n", + " hop_length: 256\n", + " win_length: 1024\n", + " n_fft: 1024\n", + " mel_spec_type: vocos # vocos | bigvgan\n", + " vocoder:\n", + " is_local: False # use local offline ckpt or not\n", + " local_path: null # local vocoder path\n", + "\n", + "ckpts:\n", + " logger: null # wandb | tensorboard | null\n", + " log_samples: True # infer random sample per save checkpoint. wip, normal to fail with extra long samples\n", + " save_per_updates: 4000 # save checkpoint per updates\n", + " keep_last_n_checkpoints: 1 # -1 to keep all, 0 to not save intermediate, > 0 to keep last N checkpoints\n", + " last_per_updates: 4000 # save last checkpoint per updates\n", + " save_dir: ckpts/${model.name}_${model.mel_spec.mel_spec_type}_${model.tokenizer}_${datasets.name}" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": { + "execution": { + "iopub.execute_input": "2025-06-16T08:41:51.536675Z", + "iopub.status.busy": "2025-06-16T08:41:51.536402Z", + "iopub.status.idle": "2025-06-16T08:41:51.666812Z", + "shell.execute_reply": "2025-06-16T08:41:51.665931Z", + "shell.execute_reply.started": "2025-06-16T08:41:51.536657Z" + }, + "trusted": true + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "hello\n" + ] + } + ], + "source": [ + "!echo hello" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": { + "execution": { + "iopub.execute_input": "2025-06-17T15:05:54.147828Z", + "iopub.status.busy": "2025-06-17T15:05:54.147535Z", + "iopub.status.idle": "2025-06-17T15:06:09.542218Z", + "shell.execute_reply": "2025-06-17T15:06:09.541348Z", + "shell.execute_reply.started": "2025-06-17T15:05:54.147805Z" + }, + "trusted": true + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "accelerate configuration saved at /root/.cache/huggingface/accelerate/default_config.yaml\n" + ] + } + ], + "source": [ + "!accelerate config default" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": { + "execution": { + "iopub.execute_input": "2025-06-17T02:33:01.507167Z", + "iopub.status.busy": "2025-06-17T02:33:01.506782Z", + "iopub.status.idle": "2025-06-17T02:33:01.644738Z", + "shell.execute_reply": "2025-06-17T02:33:01.643748Z", + "shell.execute_reply.started": "2025-06-17T02:33:01.507086Z" + }, + "trusted": true + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "go\n" + ] + } + ], + "source": [ + "!echo go" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "execution": { + "iopub.execute_input": "2025-06-17T15:16:30.232115Z", + "iopub.status.busy": "2025-06-17T15:16:30.231283Z", + "iopub.status.idle": "2025-06-17T15:18:25.550165Z", + "shell.execute_reply": "2025-06-17T15:18:25.548630Z", + "shell.execute_reply.started": "2025-06-17T15:16:30.232085Z" + }, + "trusted": true + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "copy checkpoint for finetune\n", + "\n", + "vocab : 2545\n", + "\n", + "vocoder : vocos\n", + "Using logger: None\n", + "Gradient accumulation checkpointing with per_updates now, old logic per_steps used with before f992c4e\n", + "Loading dataset ...\n", + "2025-06-17 15:17:40.763073: E external/local_xla/xla/stream_executor/cuda/cuda_fft.cc:477] Unable to register cuFFT factory: Attempting to register factory for plugin cuFFT when one has already been registered\n", + "WARNING: All log messages before absl::InitializeLog() is called are written to STDERR\n", + "E0000 00:00:1750173460.969428 249 cuda_dnn.cc:8310] Unable to register cuDNN factory: Attempting to register factory for plugin cuDNN when one has already been registered\n", + "E0000 00:00:1750173461.025851 249 cuda_blas.cc:1418] Unable to register cuBLAS factory: Attempting to register factory for plugin cuBLAS when one has already been registered\n", + "Download Vocos from huggingface charactr/vocos-mel-24khz\n", + "config.yaml: 100%|█████████████████████████████| 461/461 [00:00<00:00, 3.57MB/s]\n", + "Xet Storage is enabled for this repo, but the 'hf_xet' package is not installed. Falling back to regular HTTP download. For better performance, install the package with: `pip install huggingface_hub[hf_xet]` or `pip install hf_xet`\n", + "pytorch_model.bin: 100%|████████████████████| 54.4M/54.4M [00:00<00:00, 261MB/s]\n", + "Sorting with sampler... if slow, check whether dataset is provided with duration\n", + "Creating dynamic batches with 3200 audio frames per gpu: 100%|█| 56400/56400 [00\n", + "/usr/local/lib/python3.11/dist-packages/torch/utils/data/dataloader.py:557: UserWarning: This DataLoader will create 16 worker processes in total. Our suggested max number of worker in current system is 4, which is smaller than what this DataLoader is going to create. Please be aware that excessive worker creation might get DataLoader running slow or even freeze, lower the worker number to avoid potential slowness/freeze if necessary.\n", + " warnings.warn(_create_warning_msg(\n", + "Epoch 79/80: 0%| | 5/6182 [00:13<5:10:52, 3.02s/update, loss=0.843, update=49^C\n" + ] + } + ], + "source": [ + "# ************\n", + "!accelerate launch ./src/f5_tts/train/finetune_cli.py \\\n", + " --exp_name F5TTS_Base \\\n", + " --dataset_name vin100h-preprocessed-v2 \\\n", + " --finetune \\\n", + " --tokenizer pinyin \\\n", + " --learning_rate 1e-05 \\\n", + " --batch_size_type frame \\\n", + " --batch_size_per_gpu 3200 \\\n", + " --max_samples 64 \\\n", + " --grad_accumulation_steps 2 \\\n", + " --max_grad_norm 1 \\\n", + " --epochs 80 \\\n", + " --num_warmup_updates 2761 \\\n", + " --save_per_updates 4000 \\\n", + " --keep_last_n_checkpoints 1 \\\n", + " --last_per_updates 4000 \\\n", + " --log_samples \\\n", + " --pretrain ./ckpts/vin100h-preprocessed-v2/model_last.pt" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Copy and save" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "metadata": { + "execution": { + "iopub.execute_input": "2025-06-17T10:12:47.949751Z", + "iopub.status.busy": "2025-06-17T10:12:47.949452Z", + "iopub.status.idle": "2025-06-17T10:13:01.658980Z", + "shell.execute_reply": "2025-06-17T10:13:01.657915Z", + "shell.execute_reply.started": "2025-06-17T10:12:47.949726Z" + }, + "trusted": true + }, + "outputs": [], + "source": [ + "# *******************Importance\n", + "save_path = \"/kaggle/working/80\"\n", + "os.makedirs(save_path, exist_ok=True)\n", + "!cp -r ./ckpts/vin100h-preprocessed-v2/model_last.pt $save_path" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "metadata": { + "execution": { + "iopub.execute_input": "2025-06-17T10:16:44.769769Z", + "iopub.status.busy": "2025-06-17T10:16:44.769490Z", + "iopub.status.idle": "2025-06-17T10:18:44.924685Z", + "shell.execute_reply": "2025-06-17T10:18:44.924158Z", + "shell.execute_reply.started": "2025-06-17T10:16:44.769742Z" + }, + "trusted": true + }, + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "db02312a61864bbda76e0436a3c30d59", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "Recovering from metadata files: 0%| | 0/1 [00:00 str:\n", + " try:\n", + " checkpoint = torch.load(checkpoint_path, weights_only=True)\n", + " print(\"Original Checkpoint Keys:\", checkpoint.keys())\n", + "\n", + " to_retain = \"ema_model_state_dict\" if save_ema else \"model_state_dict\"\n", + " try:\n", + " model_state_dict_to_retain = checkpoint[to_retain]\n", + " except KeyError:\n", + " return f\"{to_retain} not found in the checkpoint.\"\n", + "\n", + " if safetensors:\n", + " new_checkpoint_path = new_checkpoint_path.replace(\".pt\", \".safetensors\")\n", + " save_file(model_state_dict_to_retain, new_checkpoint_path)\n", + " else:\n", + " new_checkpoint_path = new_checkpoint_path.replace(\".safetensors\", \".pt\")\n", + " new_checkpoint = {\"ema_model_state_dict\": model_state_dict_to_retain}\n", + " torch.save(new_checkpoint, new_checkpoint_path)\n", + "\n", + " return f\"New checkpoint saved at: {new_checkpoint_path}\"\n", + "\n", + " except Exception as e:\n", + " return f\"An error occurred: {e}\"" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "execution": { + "iopub.execute_input": "2025-05-11T14:22:24.624318Z", + "iopub.status.busy": "2025-05-11T14:22:24.623974Z", + "iopub.status.idle": "2025-05-11T14:22:30.316195Z", + "shell.execute_reply": "2025-05-11T14:22:30.315529Z", + "shell.execute_reply.started": "2025-05-11T14:22:24.624292Z" + }, + "trusted": true + }, + "outputs": [], + "source": [ + "# Prune a checkpoint after training resize model\n", + "result = prune_checkpoint(\n", + " checkpoint_path=\"/kaggle/working/F5-TTS/ckpts/vin100h-preprocessed-v2/model_last.pt\",\n", + " new_checkpoint_path=\"/root/.cache/abc.pt\",\n", + " save_ema=False,\n", + " safetensors=False\n", + ")\n", + "print(result)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Inference" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "metadata": { + "execution": { + "iopub.execute_input": "2025-06-04T09:45:21.012950Z", + "iopub.status.busy": "2025-06-04T09:45:21.012568Z", + "iopub.status.idle": "2025-06-04T09:45:21.032225Z", + "shell.execute_reply": "2025-06-04T09:45:21.031171Z", + "shell.execute_reply.started": "2025-06-04T09:45:21.012924Z" + }, + "trusted": true + }, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + " \n", + " " + ], + "text/plain": [ + "" + ] + }, + "execution_count": 20, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from IPython.display import Audio\n", + "\n", + "# Path to your audio file\n", + "audio_path = './data/vin100h-preprocessed-v2/wavs/audio_000010.wav'\n", + "\n", + "# Display and play the audio\n", + "Audio(audio_path)\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 27, + "metadata": { + "execution": { + "iopub.execute_input": "2025-06-17T15:27:26.150679Z", + "iopub.status.busy": "2025-06-17T15:27:26.150330Z", + "iopub.status.idle": "2025-06-17T15:28:18.529875Z", + "shell.execute_reply": "2025-06-17T15:28:18.528858Z", + "shell.execute_reply.started": "2025-06-17T15:27:26.150650Z" + }, + "trusted": true + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "2025-06-17 15:27:38.164110: E external/local_xla/xla/stream_executor/cuda/cuda_fft.cc:477] Unable to register cuFFT factory: Attempting to register factory for plugin cuFFT when one has already been registered\n", + "WARNING: All log messages before absl::InitializeLog() is called are written to STDERR\n", + "E0000 00:00:1750174058.189595 391 cuda_dnn.cc:8310] Unable to register cuDNN factory: Attempting to register factory for plugin cuDNN when one has already been registered\n", + "E0000 00:00:1750174058.196516 391 cuda_blas.cc:1418] Unable to register cuBLAS factory: Attempting to register factory for plugin cuBLAS when one has already been registered\n", + "Download Vocos from huggingface charactr/vocos-mel-24khz\n", + "Using vin100h-preprocessed-v2...\n", + "\n", + "vocab : ./data/vin100h-preprocessed-v2_pinyin/vocab.txt\n", + "token : custom\n", + "model : ./ckpts/vin100h-preprocessed-v2/model_last.pt \n", + "\n", + "Voice: main\n", + "ref_audio ./data/vin100h-preprocessed-v2/wavs/audio_000010.wav\n", + "Converting audio...\n", + "Audio is over 12s, clipping short. (2)\n", + "Using custom reference text...\n", + "\n", + "ref_text Về giá cả so với giá bán ngoài các siêu thị thì dâu trái ở đây rẻ hơn khá nhiều. Giả sử như bó rau ở siêu thị bán khoảng 2 đô la một bó thì ở đây chỉ có một đô la một bó. Có khi mua 50 bó được tặng thêm một bó nữa. \n", + "ref_audio_ /tmp/tmpjucisns9.wav \n", + "\n", + "\n", + "No voice tag found, using main.\n", + "Voice: main\n", + "gen_text 0 Tuy nhiên đôi khi vẫn có những trường hợp trục lợi trợ cấp khi không khai báo đầy đủ về người có nghĩa vụ chu cấp, cũng như những thay đổi về thu nhập và tài sản của mình.\n", + "\n", + "\n", + "Generating audio in 1 batches...\n", + "100%|█████████████████████████████████████████████| 1/1 [00:16<00:00, 16.82s/it]\n", + "/kaggle/working/infer_cli_basic.wav\n", + "52.37339425086975\n" + ] + } + ], + "source": [ + "import time\n", + "\n", + "t1 = time.time()\n", + "!python ./src/f5_tts/infer/infer_cli.py \\\n", + " --model \"vin100h-preprocessed-v2\" \\\n", + " --model_cfg \"./src/f5_tts/configs/vi-fine-tuned-f5-tts.yaml\" \\\n", + " --ckpt_file \"./ckpts/vin100h-preprocessed-v2/model_last.pt\" \\\n", + " --vocab_file \"./data/vin100h-preprocessed-v2_pinyin/vocab.txt\" \\\n", + " --ref_audio \"./data/vin100h-preprocessed-v2/wavs/audio_000010.wav\" \\\n", + " --ref_text \"Về giá cả so với giá bán ngoài các siêu thị thì dâu trái ở đây rẻ hơn khá nhiều. Giả sử như bó rau ở siêu thị bán khoảng 2 đô la một bó thì ở đây chỉ có một đô la một bó. Có khi mua 50 bó được tặng thêm một bó nữa.\" \\\n", + " --gen_text \"Tuy nhiên đôi khi vẫn có những trường hợp trục lợi trợ cấp khi không khai báo đầy đủ về người có nghĩa vụ chu cấp, cũng như những thay đổi về thu nhập và tài sản của mình.\" \\\n", + " --output_dir \"/kaggle/working/\"\n", + " # --output_file \"/content/abc.wav\"\n", + "\n", + "print(time.time() - t1)" + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "metadata": { + "execution": { + "iopub.execute_input": "2025-06-17T10:23:52.564882Z", + "iopub.status.busy": "2025-06-17T10:23:52.564411Z", + "iopub.status.idle": "2025-06-17T10:24:36.841824Z", + "shell.execute_reply": "2025-06-17T10:24:36.840934Z", + "shell.execute_reply.started": "2025-06-17T10:23:52.564858Z" + }, + "trusted": true + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "2025-06-17 10:24:02.873808: E external/local_xla/xla/stream_executor/cuda/cuda_fft.cc:477] Unable to register cuFFT factory: Attempting to register factory for plugin cuFFT when one has already been registered\n", + "WARNING: All log messages before absl::InitializeLog() is called are written to STDERR\n", + "E0000 00:00:1750155842.897993 500 cuda_dnn.cc:8310] Unable to register cuDNN factory: Attempting to register factory for plugin cuDNN when one has already been registered\n", + "E0000 00:00:1750155842.905125 500 cuda_blas.cc:1418] Unable to register cuBLAS factory: Attempting to register factory for plugin cuBLAS when one has already been registered\n", + "Download Vocos from huggingface charactr/vocos-mel-24khz\n", + "Using vin100h-preprocessed-v2...\n", + "\n", + "vocab : ./data/vin100h-preprocessed-v2_pinyin/vocab.txt\n", + "token : custom\n", + "model : ./ckpts/vin100h-preprocessed-v2/model_last.pt \n", + "\n", + "Voice: main\n", + "ref_audio ./data/vin100h-preprocessed-v2/wavs/audio_000010.wav\n", + "Converting audio...\n", + "Audio is over 12s, clipping short. (2)\n", + "Using custom reference text...\n", + "\n", + "ref_text Về giá cả so với giá bán ngoài các siêu thị thì dâu trái ở đây rẻ hơn khá nhiều. Giả sử như bó rau ở siêu thị bán khoảng 2 đô la một bó thì ở đây chỉ có một đô la một bó. Có khi mua 50 bó được tặng thêm một bó nữa. \n", + "ref_audio_ /tmp/tmp6_z8vr7d.wav \n", + "\n", + "\n", + "No voice tag found, using main.\n", + "Voice: main\n", + "gen_text 0 Tuy nhiên đôi khi vẫn có những trường hợp trục lợi trợ cấp khi không khai báo đầy đủ về người có nghĩa vụ chu cấp, cũng như những thay đổi về thu nhập và tài sản của mình.\n", + "\n", + "\n", + "Generating audio in 1 batches...\n", + "100%|█████████████████████████████████████████████| 1/1 [00:14<00:00, 14.86s/it]\n", + "/kaggle/working/infer_cli_basic.wav\n", + "44.271546602249146\n" + ] + } + ], + "source": [ + "import time\n", + "\n", + "t1 = time.time()\n", + "!python ./src/f5_tts/infer/infer_cli.py \\\n", + " --model \"vin100h-preprocessed-v2\" \\\n", + " --model_cfg \"./src/f5_tts/configs/F5TTS_Base.yaml\" \\\n", + " --ckpt_file \"./ckpts/vin100h-preprocessed-v2/model_last.pt\" \\\n", + " --vocab_file \"./data/vin100h-preprocessed-v2_pinyin/vocab.txt\" \\\n", + " --ref_audio \"./data/vin100h-preprocessed-v2/wavs/audio_000010.wav\" \\\n", + " --ref_text \"Về giá cả so với giá bán ngoài các siêu thị thì dâu trái ở đây rẻ hơn khá nhiều. Giả sử như bó rau ở siêu thị bán khoảng 2 đô la một bó thì ở đây chỉ có một đô la một bó. Có khi mua 50 bó được tặng thêm một bó nữa.\" \\\n", + " --gen_text \"Tuy nhiên đôi khi vẫn có những trường hợp trục lợi trợ cấp khi không khai báo đầy đủ về người có nghĩa vụ chu cấp, cũng như những thay đổi về thu nhập và tài sản của mình.\" \\\n", + " --output_dir \"/kaggle/working/\"\n", + " # --output_file \"/content/abc.wav\"\n", + "\n", + "print(time.time() - t1)" + ] + }, + { + "cell_type": "code", + "execution_count": 28, + "metadata": { + "execution": { + "iopub.execute_input": "2025-06-17T15:28:18.532293Z", + "iopub.status.busy": "2025-06-17T15:28:18.531632Z", + "iopub.status.idle": "2025-06-17T15:28:18.575767Z", + "shell.execute_reply": "2025-06-17T15:28:18.574975Z", + "shell.execute_reply.started": "2025-06-17T15:28:18.532267Z" + }, + "trusted": true + }, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + " \n", + " " + ], + "text/plain": [ + "" + ] + }, + "execution_count": 28, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from IPython.display import Audio\n", + "\n", + "# Path to your audio file\n", + "audio_path = '/kaggle/working/infer_cli_basic.wav'\n", + "\n", + "# Display and play the audio\n", + "Audio(audio_path)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Download" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": { + "execution": { + "iopub.execute_input": "2025-06-17T15:06:09.545133Z", + "iopub.status.busy": "2025-06-17T15:06:09.544801Z", + "iopub.status.idle": "2025-06-17T15:14:10.627410Z", + "shell.execute_reply": "2025-06-17T15:14:10.626697Z", + "shell.execute_reply.started": "2025-06-17T15:06:09.545102Z" + }, + "trusted": true + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Updated git hooks.\n", + "Git LFS initialized.\n" + ] + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "fec7707540b24cdc9dce3d34fb063e04", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "Fetching 2 files: 0%| | 0/2 [00:00\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "trusted": true + }, + "outputs": [], + "source": [ + "# To temporary Model hub\n", + "from huggingface_hub import HfApi\n", + "from huggingface_hub import snapshot_download\n", + "# Initialize API\n", + "api = HfApi()\n", + "\n", + "# Upload the folder to the repository root\n", + "api.upload_large_folder(\n", + " folder_path=\"/kaggle/working/save-to-huggingface\", # Local folder path\n", + " repo_id=\"heboya8/t5-tts-temp-model\",\n", + " repo_type=\"model\"\n", + ")" + ] + } + ], + "metadata": { + "kaggle": { + "accelerator": "gpu", + "dataSources": [ + { + "sourceId": 245908236, + "sourceType": "kernelVersion" + } + ], + "dockerImageVersionId": 31012, + "isGpuEnabled": true, + "isInternetEnabled": true, + "language": "python", + "sourceType": "notebook" + }, + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.11" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/notebooks/3-vi-fine-tuned-t5-tts.ipynb b/notebooks/3-vi-fine-tuned-t5-tts.ipynb new file mode 100644 index 0000000000000000000000000000000000000000..f06f4ac85692ba39dcd50d53ce2eadaae4ca1241 --- /dev/null +++ b/notebooks/3-vi-fine-tuned-t5-tts.ipynb @@ -0,0 +1,1481 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "_cell_guid": "b1076dfc-b9ad-4769-8c92-a6c4dae69d19", + "_uuid": "8f2839f25d086af736a60e9eeb907d3b93b6e0e5", + "execution": { + "iopub.execute_input": "2025-06-13T10:20:04.912431Z", + "iopub.status.busy": "2025-06-13T10:20:04.912152Z", + "iopub.status.idle": "2025-06-13T10:20:10.679130Z", + "shell.execute_reply": "2025-06-13T10:20:10.678410Z", + "shell.execute_reply.started": "2025-06-13T10:20:04.912407Z" + }, + "trusted": true + }, + "outputs": [ + { + "data": { + "text/plain": [ + "0" + ] + }, + "execution_count": 1, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "import os\n", + "os.system(\"pip install -q wget\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "execution": { + "iopub.execute_input": "2025-06-13T10:20:10.680605Z", + "iopub.status.busy": "2025-06-13T10:20:10.680405Z", + "iopub.status.idle": "2025-06-13T10:20:16.856873Z", + "shell.execute_reply": "2025-06-13T10:20:16.856251Z", + "shell.execute_reply.started": "2025-06-13T10:20:10.680587Z" + }, + "trusted": true + }, + "outputs": [], + "source": [ + "import wget\n", + "import tarfile\n", + "import torchaudio\n", + "import pandas as pd\n", + "from huggingface_hub import snapshot_download, login\n", + "login(\"\")" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": { + "execution": { + "iopub.execute_input": "2025-06-13T10:20:16.858000Z", + "iopub.status.busy": "2025-06-13T10:20:16.857573Z", + "iopub.status.idle": "2025-06-13T10:20:16.862223Z", + "shell.execute_reply": "2025-06-13T10:20:16.861466Z", + "shell.execute_reply.started": "2025-06-13T10:20:16.857972Z" + }, + "trusted": true + }, + "outputs": [], + "source": [ + "os.chdir(\"/content\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "execution": { + "iopub.execute_input": "2025-06-11T23:40:31.576197Z", + "iopub.status.busy": "2025-06-11T23:40:31.575976Z", + "iopub.status.idle": "2025-06-11T23:42:39.101348Z", + "shell.execute_reply": "2025-06-11T23:42:39.100320Z", + "shell.execute_reply.started": "2025-06-11T23:40:31.576175Z" + }, + "trusted": true + }, + "outputs": [], + "source": [ + "from huggingface_hub import HfApi\n", + "import os\n", + "api = HfApi()\n", + "repo_id = \"heboya8/t5-tts-temp-model\"\n", + "save_path = \"/content/\"\n", + "os.makedirs(save_path, exist_ok=True)\n", + "\n", + "# Download the dataset\n", + "snapshot_download(repo_id=repo_id, repo_type=\"model\", local_dir=save_path)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "execution": { + "iopub.execute_input": "2025-06-06T08:15:13.443708Z", + "iopub.status.busy": "2025-06-06T08:15:13.443085Z", + "iopub.status.idle": "2025-06-06T08:15:14.575094Z", + "shell.execute_reply": "2025-06-06T08:15:14.574083Z", + "shell.execute_reply.started": "2025-06-06T08:15:13.443681Z" + }, + "trusted": true + }, + "outputs": [], + "source": [ + "!rm -rf 45\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "execution": { + "iopub.execute_input": "2025-06-12T00:33:45.983632Z", + "iopub.status.busy": "2025-06-12T00:33:45.983423Z", + "iopub.status.idle": "2025-06-12T00:33:46.133537Z", + "shell.execute_reply": "2025-06-12T00:33:46.132461Z", + "shell.execute_reply.started": "2025-06-12T00:33:45.983614Z" + }, + "trusted": true + }, + "outputs": [], + "source": [ + "!pwd" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "execution": { + "iopub.execute_input": "2025-06-12T00:11:52.967606Z", + "iopub.status.busy": "2025-06-12T00:11:52.967417Z", + "iopub.status.idle": "2025-06-12T00:11:53.104656Z", + "shell.execute_reply": "2025-06-12T00:11:53.103880Z", + "shell.execute_reply.started": "2025-06-12T00:11:52.967585Z" + }, + "trusted": true + }, + "outputs": [], + "source": [ + "!ls -a /content/F5-TTS" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "execution": { + "iopub.execute_input": "2025-06-13T10:20:16.864545Z", + "iopub.status.busy": "2025-06-13T10:20:16.863833Z" + }, + "trusted": true + }, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Cloning into 'F5-TTS'...\n" + ] + } + ], + "source": [ + "# Step 1: Set Up the Environment\n", + "os.system(\"pip install -e . >/dev/null 2>&1\")\n", + "os.system(\"pip install torch==2.4.0+cu124 torchaudio==2.4.0+cu124 torchvision==0.19.0+cu124 --extra-index-url https://download.pytorch.org/whl/cu124 >/dev/null 2>&1\")\n", + "os.system(\"pip install accelerate==0.33.0 tensorboard >/dev/null 2>&1\")\n", + "if not os.path.exists(\"F5-TTS\"):\n", + " os.system(\"git clone https://github.com/SWivid/F5-TTS.git\")\n", + "os.chdir(\"F5-TTS\")\n", + "os.system(\"pip install -e . >/dev/null 2>&1\")\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "trusted": true + }, + "outputs": [], + "source": [ + "os.chdir(\"/content/F5-TTS\")\n", + "# os.chdir(\"F5-TTS-Vietnamese\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "execution": { + "iopub.execute_input": "2025-05-26T08:39:40.230564Z", + "iopub.status.busy": "2025-05-26T08:39:40.230243Z", + "iopub.status.idle": "2025-05-26T08:39:40.360265Z", + "shell.execute_reply": "2025-05-26T08:39:40.359565Z", + "shell.execute_reply.started": "2025-05-26T08:39:40.230535Z" + }, + "trusted": true + }, + "outputs": [], + "source": [ + "!pwd" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "trusted": true + }, + "outputs": [], + "source": [ + "!mkdir ./ckpts/vin100h-preprocessed-v2\n", + "# !cp -r /kaggle/input/vi-fine-tuned-t5-tts/22/model_last.pt \\\n", + "# ./ckpts/vin100h-preprocessed-v2/" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "trusted": true + }, + "outputs": [], + "source": [ + "!cp -r /kaggle/input/vi-fine-tuned-t5-tts/67/model_last.pt ./ckpts/vin100h-preprocessed-v2/" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": { + "execution": { + "iopub.execute_input": "2025-06-13T10:26:13.271036Z", + "iopub.status.busy": "2025-06-13T10:26:13.270379Z", + "iopub.status.idle": "2025-06-13T10:26:13.406619Z", + "shell.execute_reply": "2025-06-13T10:26:13.405565Z", + "shell.execute_reply.started": "2025-06-13T10:26:13.271003Z" + }, + "trusted": true + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + ". .. model_last.pt\n" + ] + } + ], + "source": [ + "!ls -a ./ckpts/vin100h-preprocessed-v2" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "execution": { + "iopub.execute_input": "2025-05-11T13:49:40.305365Z", + "iopub.status.busy": "2025-05-11T13:49:40.304733Z", + "iopub.status.idle": "2025-05-11T13:49:50.798803Z", + "shell.execute_reply": "2025-05-11T13:49:50.797744Z", + "shell.execute_reply.started": "2025-05-11T13:49:40.305335Z" + }, + "trusted": true + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "execution": { + "iopub.execute_input": "2025-05-10T15:59:08.329794Z", + "iopub.status.busy": "2025-05-10T15:59:08.329442Z", + "iopub.status.idle": "2025-05-10T15:59:09.362207Z", + "shell.execute_reply": "2025-05-10T15:59:09.361253Z", + "shell.execute_reply.started": "2025-05-10T15:59:08.329757Z" + }, + "trusted": true + }, + "outputs": [], + "source": [ + "import json\n", + "import os\n", + "from pathlib import Path\n", + "import shutil\n", + "import torchaudio\n", + "from datasets import load_dataset\n", + "from datasets.arrow_writer import ArrowWriter\n", + "from tqdm import tqdm\n", + "import soundfile as sf\n", + "import csv\n", + "\n", + "def save_dataset_to_local_disk(output_dir=\"./data/vin100h-preprocessed-v2\",\n", + " base_model=\"htdung167/vin100h-preprocessed-v2\",\n", + " audio_header='audio', text_header='transcription'):\n", + " \n", + " wavs_dir = os.path.join(output_dir, \"wavs\")\n", + " metadata_path = os.path.join(output_dir, \"metadata.csv\")\n", + " os.makedirs(wavs_dir, exist_ok=True)\n", + "\n", + " ds = load_dataset(base_model)['train']\n", + " metadata = []\n", + "\n", + " for idx, sample in tqdm(enumerate(ds), total=len(ds),\n", + " desc=\"Saving samples to directory\"):\n", + " audio_array = sample[audio_header]['array']\n", + " sampling_rate = sample[audio_header]['sampling_rate']\n", + " filename = f\"audio_{idx:06d}.wav\"\n", + " sf.write(os.path.join(wavs_dir, filename), audio_array, sampling_rate)\n", + " # metadata.append([f\"wavs/{filename}\", sample['preprocessed_sentence_v2']])\n", + " metadata.append([f\"wavs/{filename}\", sample[text_header]])\n", + " # metadata.append([f\"{filename}\", sample['transcription']])\n", + " \n", + " with open(metadata_path, 'w', newline='', encoding='utf-8') as f:\n", + " csv.writer(f, delimiter='|').writerows(metadata)\n", + "\n", + " print(f\"Dataset saved to {output_dir}\")\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "execution": { + "iopub.execute_input": "2025-05-10T15:59:10.399030Z", + "iopub.status.busy": "2025-05-10T15:59:10.397916Z", + "iopub.status.idle": "2025-05-10T16:10:46.269067Z", + "shell.execute_reply": "2025-05-10T16:10:46.267298Z", + "shell.execute_reply.started": "2025-05-10T15:59:10.398995Z" + }, + "trusted": true + }, + "outputs": [], + "source": [ + "output_dir = \"./data/vin100h-preprocessed-v2\"\n", + "tokenizer_type = \"pinyin\"\n", + "\n", + "save_dataset_to_local_disk(output_dir=output_dir,\n", + " base_model=\"htdung167/vin100h-preprocessed-v2\",\n", + " text_header=\"preprocessed_sentence_v2\"\n", + " )" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "execution": { + "iopub.execute_input": "2025-05-10T16:10:46.273403Z", + "iopub.status.busy": "2025-05-10T16:10:46.272176Z", + "iopub.status.idle": "2025-05-10T17:15:19.405258Z", + "shell.execute_reply": "2025-05-10T17:15:19.402002Z", + "shell.execute_reply.started": "2025-05-10T16:10:46.273366Z" + }, + "trusted": true + }, + "outputs": [], + "source": [ + "!python ./src/f5_tts/train/datasets/prepare_csv_wavs.py \\\n", + " \"./data/vin100h-preprocessed-v2\" \\\n", + " \"./data/vin100h-preprocessed-v2_pinyin\" \\\n", + " --workers 4 # Sets the number of parallel processes for preprocessing." + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": { + "execution": { + "iopub.execute_input": "2025-06-13T10:26:18.190998Z", + "iopub.status.busy": "2025-06-13T10:26:18.190662Z", + "iopub.status.idle": "2025-06-13T10:26:18.197686Z", + "shell.execute_reply": "2025-06-13T10:26:18.197059Z", + "shell.execute_reply.started": "2025-06-13T10:26:18.190974Z" + }, + "trusted": true + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Writing ./src/f5_tts/configs/vi-fine-tuned-t5-tts.yaml\n" + ] + } + ], + "source": [ + "%%writefile ./src/f5_tts/configs/vi-fine-tuned-t5-tts.yaml\n", + "hydra:\n", + " run:\n", + " dir: ckpts/${model.name}_${model.mel_spec.mel_spec_type}_${model.tokenizer}_${datasets.name}/${now:%Y-%m-%d}/${now:%H-%M-%S}\n", + "\n", + "datasets:\n", + " name: vin100h-preprocessed-v2 # dataset name\n", + " batch_size_per_gpu: 3200 # 1 GPUs, 1 * 3200 = 3200\n", + " batch_size_type: frame # frame | sample\n", + " max_samples: 64 # max sequences per batch if use frame-wise batch_size. we set 32 for small models, 64 for base models\n", + " num_workers: 4\n", + "\n", + "optim:\n", + " epochs: 10\n", + " learning_rate: 1e-5\n", + " num_warmup_updates: 2761 # warmup updates\n", + " grad_accumulation_steps: 2 # note: updates = steps / grad_accumulation_steps\n", + " max_grad_norm: 1.0 # gradient clipping\n", + " bnb_optimizer: False # use bnb 8bit AdamW optimizer or not\n", + "\n", + "model:\n", + " name: vi_fine_tuned_t5_tts # model name\n", + " tokenizer: pinyin # tokenizer type\n", + " tokenizer_path: null # if 'custom' tokenizer, define the path want to use (should be vocab.txt)\n", + " backbone: DiT\n", + " arch:\n", + " dim: 1024\n", + " depth: 22\n", + " heads: 16\n", + " ff_mult: 2\n", + " text_dim: 512\n", + " text_mask_padding: False\n", + " conv_layers: 4\n", + " pe_attn_head: 1\n", + " checkpoint_activations: False # recompute activations and save memory for extra compute\n", + " mel_spec:\n", + " target_sample_rate: 24000\n", + " n_mel_channels: 100\n", + " hop_length: 256\n", + " win_length: 1024\n", + " n_fft: 1024\n", + " mel_spec_type: vocos # vocos | bigvgan\n", + " vocoder:\n", + " is_local: False # use local offline ckpt or not\n", + " local_path: null # local vocoder path\n", + "\n", + "ckpts:\n", + " logger: null # wandb | tensorboard | null\n", + " log_samples: True # infer random sample per save checkpoint. wip, normal to fail with extra long samples\n", + " save_per_updates: 4000 # save checkpoint per updates\n", + " keep_last_n_checkpoints: 1 # -1 to keep all, 0 to not save intermediate, > 0 to keep last N checkpoints\n", + " last_per_updates: 4000 # save last checkpoint per updates\n", + " save_dir: ckpts/${model.name}_${model.mel_spec.mel_spec_type}_${model.tokenizer}_${datasets.name}" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": { + "execution": { + "iopub.execute_input": "2025-06-13T10:26:20.826223Z", + "iopub.status.busy": "2025-06-13T10:26:20.825492Z", + "iopub.status.idle": "2025-06-13T10:26:34.766936Z", + "shell.execute_reply": "2025-06-13T10:26:34.766089Z", + "shell.execute_reply.started": "2025-06-13T10:26:20.826201Z" + }, + "trusted": true + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "accelerate configuration saved at /root/.cache/huggingface/accelerate/default_config.yaml\n" + ] + } + ], + "source": [ + "!accelerate config default" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": { + "execution": { + "iopub.execute_input": "2025-06-13T02:56:29.790731Z", + "iopub.status.busy": "2025-06-13T02:56:29.790473Z", + "iopub.status.idle": "2025-06-13T02:56:29.925932Z", + "shell.execute_reply": "2025-06-13T02:56:29.924747Z", + "shell.execute_reply.started": "2025-06-13T02:56:29.790710Z" + }, + "trusted": true + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "go\n" + ] + } + ], + "source": [ + "!echo go" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": { + "execution": { + "iopub.execute_input": "2025-06-13T02:56:29.927624Z", + "iopub.status.busy": "2025-06-13T02:56:29.927275Z", + "iopub.status.idle": "2025-06-13T02:56:30.847599Z", + "shell.execute_reply": "2025-06-13T02:56:30.846603Z", + "shell.execute_reply.started": "2025-06-13T02:56:29.927588Z" + }, + "trusted": true + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "/content/F5-TTS\n" + ] + } + ], + "source": [ + "!pwd" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": { + "execution": { + "iopub.status.idle": "2025-06-13T17:27:16.747386Z", + "shell.execute_reply": "2025-06-13T17:27:16.744608Z", + "shell.execute_reply.started": "2025-06-13T10:29:35.611744Z" + }, + "trusted": true + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "Epoch 69/69: 97%|▉| 6010/6182 [3:21:35<05:35, 1.95s/update, loss=0.613, update\u001b[A\n", + "Epoch 69/69: 97%|▉| 6010/6182 [3:21:35<05:35, 1.95s/update, loss=0.629, update\u001b[A\n", + "Epoch 69/69: 97%|▉| 6011/6182 [3:21:37<05:17, 1.86s/update, loss=0.629, update\u001b[A\n", + "Epoch 69/69: 97%|▉| 6011/6182 [3:21:37<05:17, 1.86s/update, loss=0.688, update\u001b[A\n", + "Epoch 69/69: 97%|▉| 6012/6182 [3:21:39<05:23, 1.90s/update, loss=0.688, update\u001b[A\n", + "Epoch 69/69: 97%|▉| 6012/6182 [3:21:39<05:23, 1.90s/update, loss=0.433, update\u001b[A\n", + "Epoch 69/69: 97%|▉| 6013/6182 [3:21:41<05:33, 1.97s/update, loss=0.433, update\u001b[A\n", + "Epoch 69/69: 97%|▉| 6013/6182 [3:21:41<05:33, 1.97s/update, loss=0.586, update\u001b[A\n", + "Epoch 69/69: 97%|▉| 6014/6182 [3:21:43<05:19, 1.90s/update, loss=0.586, update\u001b[A\n", + "Epoch 69/69: 97%|▉| 6014/6182 [3:21:43<05:19, 1.90s/update, loss=0.69, update=\u001b[A\n", + "Epoch 69/69: 97%|▉| 6015/6182 [3:21:45<05:18, 1.90s/update, loss=0.69, update=\u001b[A\n", + "Epoch 69/69: 97%|▉| 6015/6182 [3:21:45<05:18, 1.90s/update, loss=0.744, update\u001b[A\n", + "Epoch 69/69: 97%|▉| 6016/6182 [3:21:47<05:04, 1.83s/update, loss=0.744, update\u001b[A\n", + "Epoch 69/69: 97%|▉| 6016/6182 [3:21:47<05:04, 1.83s/update, loss=0.466, update\u001b[A\n", + "Epoch 69/69: 97%|▉| 6017/6182 [3:21:49<05:20, 1.94s/update, loss=0.466, update\u001b[A\n", + "Epoch 69/69: 97%|▉| 6017/6182 [3:21:49<05:20, 1.94s/update, loss=0.421, update\u001b[A\n", + "Epoch 69/69: 97%|▉| 6018/6182 [3:21:51<05:11, 1.90s/update, loss=0.421, update\u001b[A\n", + "Epoch 69/69: 97%|▉| 6018/6182 [3:21:51<05:11, 1.90s/update, loss=0.691, update\u001b[A\n", + "Epoch 69/69: 97%|▉| 6019/6182 [3:21:52<04:53, 1.80s/update, loss=0.691, update\u001b[A\n", + "Epoch 69/69: 97%|▉| 6019/6182 [3:21:52<04:53, 1.80s/update, loss=0.566, update\u001b[A\n", + "Epoch 69/69: 97%|▉| 6020/6182 [3:21:54<04:48, 1.78s/update, loss=0.566, update\u001b[A\n", + "Epoch 69/69: 97%|▉| 6020/6182 [3:21:54<04:48, 1.78s/update, loss=0.529, update\u001b[A\n", + "Epoch 69/69: 97%|▉| 6021/6182 [3:21:56<04:54, 1.83s/update, loss=0.529, update\u001b[A\n", + "Epoch 69/69: 97%|▉| 6021/6182 [3:21:56<04:54, 1.83s/update, loss=0.493, update\u001b[A\n", + "Epoch 69/69: 97%|▉| 6022/6182 [3:21:58<04:58, 1.86s/update, loss=0.493, update\u001b[A\n", + "Epoch 69/69: 97%|▉| 6022/6182 [3:21:58<04:58, 1.86s/update, loss=0.853, update\u001b[A\n", + "Epoch 69/69: 97%|▉| 6023/6182 [3:21:59<04:43, 1.79s/update, loss=0.853, update\u001b[A\n", + "Epoch 69/69: 97%|▉| 6023/6182 [3:21:59<04:43, 1.79s/update, loss=0.494, update\u001b[A\n", + "Epoch 69/69: 97%|▉| 6024/6182 [3:22:01<04:37, 1.76s/update, loss=0.494, update\u001b[A\n", + "Epoch 69/69: 97%|▉| 6024/6182 [3:22:01<04:37, 1.76s/update, loss=0.574, update\u001b[A\n", + "Epoch 69/69: 97%|▉| 6025/6182 [3:22:03<04:44, 1.81s/update, loss=0.574, update\u001b[A\n", + "Epoch 69/69: 97%|▉| 6025/6182 [3:22:03<04:44, 1.81s/update, loss=0.522, update\u001b[A\n", + "Epoch 69/69: 97%|▉| 6026/6182 [3:22:05<04:36, 1.77s/update, loss=0.522, update\u001b[A\n", + "Epoch 69/69: 97%|▉| 6026/6182 [3:22:05<04:36, 1.77s/update, loss=0.66, update=\u001b[A\n", + "Epoch 69/69: 97%|▉| 6027/6182 [3:22:07<04:44, 1.84s/update, loss=0.66, update=\u001b[A\n", + "Epoch 69/69: 97%|▉| 6027/6182 [3:22:07<04:44, 1.84s/update, loss=0.515, update\u001b[A\n", + "Epoch 69/69: 98%|▉| 6028/6182 [3:22:08<04:35, 1.79s/update, loss=0.515, update\u001b[A\n", + "Epoch 69/69: 98%|▉| 6028/6182 [3:22:08<04:35, 1.79s/update, loss=0.562, update\u001b[A\n", + "Epoch 69/69: 98%|▉| 6029/6182 [3:22:10<04:46, 1.87s/update, loss=0.562, update\u001b[A\n", + "Epoch 69/69: 98%|▉| 6029/6182 [3:22:10<04:46, 1.87s/update, loss=0.721, update\u001b[A\n", + "Epoch 69/69: 98%|▉| 6030/6182 [3:22:12<04:37, 1.82s/update, loss=0.721, update\u001b[A\n", + "Epoch 69/69: 98%|▉| 6030/6182 [3:22:12<04:37, 1.82s/update, loss=0.551, update\u001b[A\n", + "Epoch 69/69: 98%|▉| 6031/6182 [3:22:14<04:28, 1.78s/update, loss=0.551, update\u001b[A\n", + "Epoch 69/69: 98%|▉| 6031/6182 [3:22:14<04:28, 1.78s/update, loss=0.426, update\u001b[A\n", + "Epoch 69/69: 98%|▉| 6032/6182 [3:22:16<05:09, 2.07s/update, loss=0.426, update\u001b[A\n", + "Epoch 69/69: 98%|▉| 6032/6182 [3:22:16<05:09, 2.07s/update, loss=0.579, update\u001b[A\n", + "Epoch 69/69: 98%|▉| 6033/6182 [3:22:18<04:56, 1.99s/update, loss=0.579, update\u001b[A\n", + "Epoch 69/69: 98%|▉| 6033/6182 [3:22:18<04:56, 1.99s/update, loss=0.658, update\u001b[A\n", + "Epoch 69/69: 98%|▉| 6034/6182 [3:22:20<04:43, 1.92s/update, loss=0.658, update\u001b[A\n", + "Epoch 69/69: 98%|▉| 6034/6182 [3:22:20<04:43, 1.92s/update, loss=0.515, update\u001b[A\n", + "Epoch 69/69: 98%|▉| 6035/6182 [3:22:22<04:37, 1.89s/update, loss=0.515, update\u001b[A\n", + "Epoch 69/69: 98%|▉| 6035/6182 [3:22:22<04:37, 1.89s/update, loss=0.555, update\u001b[A\n", + "Epoch 69/69: 98%|▉| 6036/6182 [3:22:25<05:17, 2.17s/update, loss=0.555, update\u001b[A\n", + "Epoch 69/69: 98%|▉| 6036/6182 [3:22:25<05:17, 2.17s/update, loss=0.789, update\u001b[A\n", + "Epoch 69/69: 98%|▉| 6037/6182 [3:22:27<05:13, 2.16s/update, loss=0.789, update\u001b[A\n", + "Epoch 69/69: 98%|▉| 6037/6182 [3:22:27<05:13, 2.16s/update, loss=0.62, update=\u001b[A\n", + "Epoch 69/69: 98%|▉| 6038/6182 [3:22:29<04:56, 2.06s/update, loss=0.62, update=\u001b[A\n", + "Epoch 69/69: 98%|▉| 6038/6182 [3:22:29<04:56, 2.06s/update, loss=0.447, update\u001b[A\n", + "Epoch 69/69: 98%|▉| 6039/6182 [3:22:30<04:35, 1.93s/update, loss=0.447, update\u001b[A\n", + "Epoch 69/69: 98%|▉| 6039/6182 [3:22:30<04:35, 1.93s/update, loss=0.523, update\u001b[A\n", + "Epoch 69/69: 98%|▉| 6040/6182 [3:22:33<05:05, 2.15s/update, loss=0.523, update\u001b[A\n", + "Epoch 69/69: 98%|▉| 6040/6182 [3:22:33<05:05, 2.15s/update, loss=0.783, update\u001b[A\n", + "Epoch 69/69: 98%|▉| 6041/6182 [3:22:35<04:54, 2.09s/update, loss=0.783, update\u001b[A\n", + "Epoch 69/69: 98%|▉| 6041/6182 [3:22:35<04:54, 2.09s/update, loss=0.5, update=4\u001b[A\n", + "Epoch 69/69: 98%|▉| 6042/6182 [3:22:36<04:29, 1.92s/update, loss=0.5, update=4\u001b[A\n", + "Epoch 69/69: 98%|▉| 6042/6182 [3:22:36<04:29, 1.92s/update, loss=0.762, update\u001b[A\n", + "Epoch 69/69: 98%|▉| 6043/6182 [3:22:39<04:49, 2.08s/update, loss=0.762, update\u001b[A\n", + "Epoch 69/69: 98%|▉| 6043/6182 [3:22:39<04:49, 2.08s/update, loss=0.426, update\u001b[A\n", + "Epoch 69/69: 98%|▉| 6044/6182 [3:22:41<04:28, 1.95s/update, loss=0.426, update\u001b[A\n", + "Epoch 69/69: 98%|▉| 6044/6182 [3:22:41<04:28, 1.95s/update, loss=0.683, update\u001b[A\n", + "Epoch 69/69: 98%|▉| 6045/6182 [3:22:43<04:38, 2.03s/update, loss=0.683, update\u001b[A\n", + "Epoch 69/69: 98%|▉| 6045/6182 [3:22:43<04:38, 2.03s/update, loss=0.515, update\u001b[A\n", + "Epoch 69/69: 98%|▉| 6046/6182 [3:22:45<04:28, 1.98s/update, loss=0.515, update\u001b[A\n", + "Epoch 69/69: 98%|▉| 6046/6182 [3:22:45<04:28, 1.98s/update, loss=0.502, update\u001b[A\n", + "Epoch 69/69: 98%|▉| 6047/6182 [3:22:47<04:32, 2.02s/update, loss=0.502, update\u001b[A\n", + "Epoch 69/69: 98%|▉| 6047/6182 [3:22:47<04:32, 2.02s/update, loss=0.492, update\u001b[A\n", + "Epoch 69/69: 98%|▉| 6048/6182 [3:22:48<04:13, 1.89s/update, loss=0.492, update\u001b[A\n", + "Epoch 69/69: 98%|▉| 6048/6182 [3:22:48<04:13, 1.89s/update, loss=0.471, update\u001b[A\n", + "Epoch 69/69: 98%|▉| 6049/6182 [3:22:50<04:06, 1.85s/update, loss=0.471, update\u001b[A\n", + "Epoch 69/69: 98%|▉| 6049/6182 [3:22:50<04:06, 1.85s/update, loss=0.68, update=\u001b[A\n", + "Epoch 69/69: 98%|▉| 6050/6182 [3:22:53<04:41, 2.13s/update, loss=0.68, update=\u001b[A\n", + "Epoch 69/69: 98%|▉| 6050/6182 [3:22:53<04:41, 2.13s/update, loss=0.532, update\u001b[A\n", + "Epoch 69/69: 98%|▉| 6051/6182 [3:22:55<04:24, 2.02s/update, loss=0.532, update\u001b[A\n", + "Epoch 69/69: 98%|▉| 6051/6182 [3:22:55<04:24, 2.02s/update, loss=0.515, update\u001b[A\n", + "Epoch 69/69: 98%|▉| 6052/6182 [3:22:57<04:18, 1.99s/update, loss=0.515, update\u001b[A\n", + "Epoch 69/69: 98%|▉| 6052/6182 [3:22:57<04:18, 1.99s/update, loss=0.491, update\u001b[A\n", + "Epoch 69/69: 98%|▉| 6053/6182 [3:22:58<04:06, 1.91s/update, loss=0.491, update\u001b[A\n", + "Epoch 69/69: 98%|▉| 6053/6182 [3:22:58<04:06, 1.91s/update, loss=0.503, update\u001b[A\n", + "Epoch 69/69: 98%|▉| 6054/6182 [3:23:00<04:07, 1.93s/update, loss=0.503, update\u001b[A\n", + "Epoch 69/69: 98%|▉| 6054/6182 [3:23:00<04:07, 1.93s/update, loss=0.517, update\u001b[A\n", + "Epoch 69/69: 98%|▉| 6055/6182 [3:23:02<04:03, 1.92s/update, loss=0.517, update\u001b[A\n", + "Epoch 69/69: 98%|▉| 6055/6182 [3:23:02<04:03, 1.92s/update, loss=0.8, update=4\u001b[A\n", + "Epoch 69/69: 98%|▉| 6056/6182 [3:23:04<04:16, 2.03s/update, loss=0.8, update=4\u001b[A\n", + "Epoch 69/69: 98%|▉| 6056/6182 [3:23:04<04:16, 2.03s/update, loss=0.654, update\u001b[A\n", + "Epoch 69/69: 98%|▉| 6057/6182 [3:23:06<04:02, 1.94s/update, loss=0.654, update\u001b[A\n", + "Epoch 69/69: 98%|▉| 6057/6182 [3:23:06<04:02, 1.94s/update, loss=0.669, update\u001b[A\n", + "Epoch 69/69: 98%|▉| 6058/6182 [3:23:08<03:49, 1.85s/update, loss=0.669, update\u001b[A\n", + "Epoch 69/69: 98%|▉| 6058/6182 [3:23:08<03:49, 1.85s/update, loss=0.555, update\u001b[A\n", + "Epoch 69/69: 98%|▉| 6059/6182 [3:23:10<03:50, 1.87s/update, loss=0.555, update\u001b[A\n", + "Epoch 69/69: 98%|▉| 6059/6182 [3:23:10<03:50, 1.87s/update, loss=0.533, update\u001b[A\n", + "Epoch 69/69: 98%|▉| 6060/6182 [3:23:12<03:47, 1.86s/update, loss=0.533, update\u001b[A\n", + "Epoch 69/69: 98%|▉| 6060/6182 [3:23:12<03:47, 1.86s/update, loss=0.74, update=\u001b[A\n", + "Epoch 69/69: 98%|▉| 6061/6182 [3:23:13<03:46, 1.88s/update, loss=0.74, update=\u001b[A\n", + "Epoch 69/69: 98%|▉| 6061/6182 [3:23:13<03:46, 1.88s/update, loss=0.991, update\u001b[A\n", + "Epoch 69/69: 98%|▉| 6062/6182 [3:23:15<03:40, 1.84s/update, loss=0.991, update\u001b[A\n", + "Epoch 69/69: 98%|▉| 6062/6182 [3:23:15<03:40, 1.84s/update, loss=0.505, update\u001b[A\n", + "Epoch 69/69: 98%|▉| 6063/6182 [3:23:17<03:49, 1.93s/update, loss=0.505, update\u001b[A\n", + "Epoch 69/69: 98%|▉| 6063/6182 [3:23:17<03:49, 1.93s/update, loss=0.448, update\u001b[A\n", + "Epoch 69/69: 98%|▉| 6064/6182 [3:23:19<03:49, 1.95s/update, loss=0.448, update\u001b[A\n", + "Epoch 69/69: 98%|▉| 6064/6182 [3:23:19<03:49, 1.95s/update, loss=0.822, update\u001b[A\n", + "Epoch 69/69: 98%|▉| 6065/6182 [3:23:21<03:38, 1.86s/update, loss=0.822, update\u001b[A\n", + "Epoch 69/69: 98%|▉| 6065/6182 [3:23:21<03:38, 1.86s/update, loss=0.59, update=\u001b[A\n", + "Epoch 69/69: 98%|▉| 6066/6182 [3:23:23<03:30, 1.82s/update, loss=0.59, update=\u001b[A\n", + "Epoch 69/69: 98%|▉| 6066/6182 [3:23:23<03:30, 1.82s/update, loss=0.525, update\u001b[A\n", + "Epoch 69/69: 98%|▉| 6067/6182 [3:23:25<03:53, 2.03s/update, loss=0.525, update\u001b[A\n", + "Epoch 69/69: 98%|▉| 6067/6182 [3:23:25<03:53, 2.03s/update, loss=0.572, update\u001b[A\n", + "Epoch 69/69: 98%|▉| 6068/6182 [3:23:27<03:57, 2.09s/update, loss=0.572, update\u001b[A\n", + "Epoch 69/69: 98%|▉| 6068/6182 [3:23:27<03:57, 2.09s/update, loss=0.525, update\u001b[A\n", + "Epoch 69/69: 98%|▉| 6069/6182 [3:23:29<03:41, 1.96s/update, loss=0.525, update\u001b[A\n", + "Epoch 69/69: 98%|▉| 6069/6182 [3:23:29<03:41, 1.96s/update, loss=0.595, update\u001b[A\n", + "Epoch 69/69: 98%|▉| 6070/6182 [3:23:31<03:30, 1.88s/update, loss=0.595, update\u001b[A\n", + "Epoch 69/69: 98%|▉| 6070/6182 [3:23:31<03:30, 1.88s/update, loss=0.705, update\u001b[A\n", + "Epoch 69/69: 98%|▉| 6071/6182 [3:23:33<03:28, 1.88s/update, loss=0.705, update\u001b[A\n", + "Epoch 69/69: 98%|▉| 6071/6182 [3:23:33<03:28, 1.88s/update, loss=0.642, update\u001b[A\n", + "Epoch 69/69: 98%|▉| 6072/6182 [3:23:34<03:24, 1.86s/update, loss=0.642, update\u001b[A\n", + "Epoch 69/69: 98%|▉| 6072/6182 [3:23:34<03:24, 1.86s/update, loss=0.591, update\u001b[A\n", + "Epoch 69/69: 98%|▉| 6073/6182 [3:23:36<03:15, 1.79s/update, loss=0.591, update\u001b[A\n", + "Epoch 69/69: 98%|▉| 6073/6182 [3:23:36<03:15, 1.79s/update, loss=0.502, update\u001b[A\n", + "Epoch 69/69: 98%|▉| 6074/6182 [3:23:38<03:30, 1.95s/update, loss=0.502, update\u001b[A\n", + "Epoch 69/69: 98%|▉| 6074/6182 [3:23:38<03:30, 1.95s/update, loss=0.615, update\u001b[A\n", + "Epoch 69/69: 98%|▉| 6075/6182 [3:23:40<03:13, 1.81s/update, loss=0.615, update\u001b[A\n", + "Epoch 69/69: 98%|▉| 6075/6182 [3:23:40<03:13, 1.81s/update, loss=0.612, update\u001b[A\n", + "Epoch 69/69: 98%|▉| 6076/6182 [3:23:42<03:13, 1.83s/update, loss=0.612, update\u001b[A\n", + "Epoch 69/69: 98%|▉| 6076/6182 [3:23:42<03:13, 1.83s/update, loss=0.712, update\u001b[A\n", + "Epoch 69/69: 98%|▉| 6077/6182 [3:23:44<03:30, 2.01s/update, loss=0.712, update\u001b[A\n", + "Epoch 69/69: 98%|▉| 6077/6182 [3:23:44<03:30, 2.01s/update, loss=1.18, update=\u001b[A\n", + "Epoch 69/69: 98%|▉| 6078/6182 [3:23:46<03:34, 2.06s/update, loss=1.18, update=\u001b[A\n", + "Epoch 69/69: 98%|▉| 6078/6182 [3:23:46<03:34, 2.06s/update, loss=1.66, update=\u001b[A\n", + "Epoch 69/69: 98%|▉| 6079/6182 [3:23:48<03:21, 1.96s/update, loss=1.66, update=\u001b[A\n", + "Epoch 69/69: 98%|▉| 6079/6182 [3:23:48<03:21, 1.96s/update, loss=0.728, update\u001b[A\n", + "Epoch 69/69: 98%|▉| 6080/6182 [3:23:50<03:07, 1.84s/update, loss=0.728, update\u001b[A\n", + "Epoch 69/69: 98%|▉| 6080/6182 [3:23:50<03:07, 1.84s/update, loss=0.621, update\u001b[A\n", + "Epoch 69/69: 98%|▉| 6081/6182 [3:23:51<03:03, 1.81s/update, loss=0.621, update\u001b[A\n", + "Epoch 69/69: 98%|▉| 6081/6182 [3:23:51<03:03, 1.81s/update, loss=0.586, update\u001b[A\n", + "Epoch 69/69: 98%|▉| 6082/6182 [3:23:53<02:55, 1.76s/update, loss=0.586, update\u001b[A\n", + "Epoch 69/69: 98%|▉| 6082/6182 [3:23:53<02:55, 1.76s/update, loss=0.728, update\u001b[A\n", + "Epoch 69/69: 98%|▉| 6083/6182 [3:23:55<02:56, 1.78s/update, loss=0.728, update\u001b[A\n", + "Epoch 69/69: 98%|▉| 6083/6182 [3:23:55<02:56, 1.78s/update, loss=0.514, update\u001b[A\n", + "Epoch 69/69: 98%|▉| 6084/6182 [3:23:57<02:52, 1.76s/update, loss=0.514, update\u001b[A\n", + "Epoch 69/69: 98%|▉| 6084/6182 [3:23:57<02:52, 1.76s/update, loss=0.567, update\u001b[A\n", + "Epoch 69/69: 98%|▉| 6085/6182 [3:23:59<02:55, 1.81s/update, loss=0.567, update\u001b[A\n", + "Epoch 69/69: 98%|▉| 6085/6182 [3:23:59<02:55, 1.81s/update, loss=0.547, update\u001b[A\n", + "Epoch 69/69: 98%|▉| 6086/6182 [3:24:00<02:51, 1.79s/update, loss=0.547, update\u001b[A\n", + "Epoch 69/69: 98%|▉| 6086/6182 [3:24:00<02:51, 1.79s/update, loss=0.862, update\u001b[A\n", + "Epoch 69/69: 98%|▉| 6087/6182 [3:24:02<02:53, 1.83s/update, loss=0.862, update\u001b[A\n", + "Epoch 69/69: 98%|▉| 6087/6182 [3:24:02<02:53, 1.83s/update, loss=0.519, update\u001b[A\n", + "Epoch 69/69: 98%|▉| 6088/6182 [3:24:04<02:56, 1.88s/update, loss=0.519, update\u001b[A\n", + "Epoch 69/69: 98%|▉| 6088/6182 [3:24:04<02:56, 1.88s/update, loss=0.497, update\u001b[A\n", + "Epoch 69/69: 98%|▉| 6089/6182 [3:24:07<03:08, 2.03s/update, loss=0.497, update\u001b[A\n", + "Epoch 69/69: 98%|▉| 6089/6182 [3:24:07<03:08, 2.03s/update, loss=0.67, update=\u001b[A\n", + "Epoch 69/69: 99%|▉| 6090/6182 [3:24:08<02:59, 1.96s/update, loss=0.67, update=\u001b[A\n", + "Epoch 69/69: 99%|▉| 6090/6182 [3:24:08<02:59, 1.96s/update, loss=0.458, update\u001b[A\n", + "Epoch 69/69: 99%|▉| 6091/6182 [3:24:10<02:35, 1.71s/update, loss=0.458, update\u001b[A\n", + "Epoch 69/69: 99%|▉| 6091/6182 [3:24:10<02:35, 1.71s/update, loss=0.382, update\u001b[A\n", + "Epoch 69/69: 99%|▉| 6092/6182 [3:24:11<02:31, 1.69s/update, loss=0.382, update\u001b[A\n", + "Epoch 69/69: 99%|▉| 6092/6182 [3:24:11<02:31, 1.69s/update, loss=0.651, update\u001b[A\n", + "Epoch 69/69: 99%|▉| 6093/6182 [3:24:13<02:39, 1.79s/update, loss=0.651, update\u001b[A\n", + "Epoch 69/69: 99%|▉| 6093/6182 [3:24:13<02:39, 1.79s/update, loss=0.521, update\u001b[A\n", + "Epoch 69/69: 99%|▉| 6094/6182 [3:24:15<02:34, 1.76s/update, loss=0.521, update\u001b[A\n", + "Epoch 69/69: 99%|▉| 6094/6182 [3:24:15<02:34, 1.76s/update, loss=0.507, update\u001b[A\n", + "Epoch 69/69: 99%|▉| 6095/6182 [3:24:17<02:30, 1.73s/update, loss=0.507, update\u001b[A\n", + "Epoch 69/69: 99%|▉| 6095/6182 [3:24:17<02:30, 1.73s/update, loss=0.633, update\u001b[A\n", + "Epoch 69/69: 99%|▉| 6096/6182 [3:24:20<03:01, 2.12s/update, loss=0.633, update\u001b[A\n", + "Epoch 69/69: 99%|▉| 6096/6182 [3:24:20<03:01, 2.12s/update, loss=0.476, update\u001b[A\n", + "Epoch 69/69: 99%|▉| 6097/6182 [3:24:21<02:55, 2.06s/update, loss=0.476, update\u001b[A\n", + "Epoch 69/69: 99%|▉| 6097/6182 [3:24:21<02:55, 2.06s/update, loss=0.528, update\u001b[A\n", + "Epoch 69/69: 99%|▉| 6098/6182 [3:24:23<02:51, 2.05s/update, loss=0.528, update\u001b[A\n", + "Epoch 69/69: 99%|▉| 6098/6182 [3:24:23<02:51, 2.05s/update, loss=0.916, update\u001b[A\n", + "Epoch 69/69: 99%|▉| 6099/6182 [3:24:25<02:37, 1.89s/update, loss=0.916, update\u001b[A\n", + "Epoch 69/69: 99%|▉| 6099/6182 [3:24:25<02:37, 1.89s/update, loss=0.679, update\u001b[A\n", + "Epoch 69/69: 99%|▉| 6100/6182 [3:24:27<02:34, 1.89s/update, loss=0.679, update\u001b[A\n", + "Epoch 69/69: 99%|▉| 6100/6182 [3:24:27<02:34, 1.89s/update, loss=0.555, update\u001b[A\n", + "Epoch 69/69: 99%|▉| 6101/6182 [3:24:29<02:28, 1.84s/update, loss=0.555, update\u001b[A\n", + "Epoch 69/69: 99%|▉| 6101/6182 [3:24:29<02:28, 1.84s/update, loss=0.559, update\u001b[A\n", + "Epoch 69/69: 99%|▉| 6102/6182 [3:24:31<02:33, 1.92s/update, loss=0.559, update\u001b[A\n", + "Epoch 69/69: 99%|▉| 6102/6182 [3:24:31<02:33, 1.92s/update, loss=0.737, update\u001b[A\n", + "Epoch 69/69: 99%|▉| 6103/6182 [3:24:33<02:38, 2.00s/update, loss=0.737, update\u001b[A\n", + "Epoch 69/69: 99%|▉| 6103/6182 [3:24:33<02:38, 2.00s/update, loss=0.602, update\u001b[A\n", + "Epoch 69/69: 99%|▉| 6104/6182 [3:24:35<02:27, 1.89s/update, loss=0.602, update\u001b[A\n", + "Epoch 69/69: 99%|▉| 6104/6182 [3:24:35<02:27, 1.89s/update, loss=0.455, update\u001b[A\n", + "Epoch 69/69: 99%|▉| 6105/6182 [3:24:36<02:24, 1.88s/update, loss=0.455, update\u001b[A\n", + "Epoch 69/69: 99%|▉| 6105/6182 [3:24:36<02:24, 1.88s/update, loss=0.625, update\u001b[A\n", + "Epoch 69/69: 99%|▉| 6106/6182 [3:24:38<02:18, 1.82s/update, loss=0.625, update\u001b[A\n", + "Epoch 69/69: 99%|▉| 6106/6182 [3:24:38<02:18, 1.82s/update, loss=0.527, update\u001b[A\n", + "Epoch 69/69: 100%|▉| 6153/6182 [3:26:15<01:00, 2.10s/update, loss=0.477, update\u001b[A\n", + "Epoch 69/69: 100%|▉| 6153/6182 [3:26:15<01:00, 2.10s/update, loss=0.631, update\u001b[A\n", + "Epoch 69/69: 100%|▉| 6154/6182 [3:26:17<00:58, 2.07s/update, loss=0.631, update\u001b[A\n", + "Epoch 69/69: 100%|▉| 6154/6182 [3:26:17<00:58, 2.07s/update, loss=0.592, update\u001b[A\n", + "Epoch 69/69: 100%|▉| 6155/6182 [3:26:19<00:55, 2.06s/update, loss=0.592, update\u001b[A\n", + "Epoch 69/69: 100%|▉| 6155/6182 [3:26:19<00:55, 2.06s/update, loss=0.563, update\u001b[A\n", + "Epoch 69/69: 100%|▉| 6156/6182 [3:26:21<00:52, 2.04s/update, loss=0.563, update\u001b[A\n", + "Epoch 69/69: 100%|▉| 6156/6182 [3:26:21<00:52, 2.04s/update, loss=0.722, update\u001b[A\n", + "Epoch 69/69: 100%|▉| 6157/6182 [3:26:23<00:49, 1.97s/update, loss=0.722, update\u001b[A\n", + "Epoch 69/69: 100%|▉| 6157/6182 [3:26:23<00:49, 1.97s/update, loss=0.542, update\u001b[A\n", + "Epoch 69/69: 100%|▉| 6158/6182 [3:26:25<00:49, 2.07s/update, loss=0.542, update\u001b[A\n", + "Epoch 69/69: 100%|▉| 6158/6182 [3:26:25<00:49, 2.07s/update, loss=0.659, update\u001b[A\n", + "Epoch 69/69: 100%|▉| 6159/6182 [3:26:27<00:48, 2.09s/update, loss=0.659, update\u001b[A\n", + "Epoch 69/69: 100%|▉| 6159/6182 [3:26:27<00:48, 2.09s/update, loss=0.584, update\u001b[A\n", + "Epoch 69/69: 100%|▉| 6160/6182 [3:26:29<00:43, 1.99s/update, loss=0.584, update\u001b[A\n", + "Epoch 69/69: 100%|▉| 6160/6182 [3:26:29<00:43, 1.99s/update, loss=1.07, update=\u001b[A\n", + "Epoch 69/69: 100%|▉| 6161/6182 [3:26:31<00:41, 1.95s/update, loss=1.07, update=\u001b[A\n", + "Epoch 69/69: 100%|▉| 6161/6182 [3:26:31<00:41, 1.95s/update, loss=0.566, update\u001b[A\n", + "Epoch 69/69: 100%|▉| 6162/6182 [3:26:33<00:37, 1.87s/update, loss=0.566, update\u001b[A\n", + "Epoch 69/69: 100%|▉| 6162/6182 [3:26:33<00:37, 1.87s/update, loss=0.58, update=\u001b[A\n", + "Epoch 69/69: 100%|▉| 6163/6182 [3:26:34<00:35, 1.85s/update, loss=0.58, update=\u001b[A\n", + "Epoch 69/69: 100%|▉| 6163/6182 [3:26:34<00:35, 1.85s/update, loss=0.49, update=\u001b[A\n", + "Epoch 69/69: 100%|▉| 6164/6182 [3:26:37<00:36, 2.05s/update, loss=0.49, update=\u001b[A\n", + "Epoch 69/69: 100%|▉| 6164/6182 [3:26:37<00:36, 2.05s/update, loss=1.05, update=\u001b[A\n", + "Epoch 69/69: 100%|▉| 6165/6182 [3:26:39<00:34, 2.00s/update, loss=1.05, update=\u001b[A\n", + "Epoch 69/69: 100%|▉| 6165/6182 [3:26:39<00:34, 2.00s/update, loss=0.794, update\u001b[A\n", + "Epoch 69/69: 100%|▉| 6166/6182 [3:26:41<00:30, 1.92s/update, loss=0.794, update\u001b[A\n", + "Epoch 69/69: 100%|▉| 6166/6182 [3:26:41<00:30, 1.92s/update, loss=0.689, update\u001b[A\n", + "Epoch 69/69: 100%|▉| 6167/6182 [3:26:42<00:27, 1.84s/update, loss=0.689, update\u001b[A\n", + "Epoch 69/69: 100%|▉| 6167/6182 [3:26:42<00:27, 1.84s/update, loss=0.665, update\u001b[A\n", + "Epoch 69/69: 100%|▉| 6168/6182 [3:26:44<00:25, 1.81s/update, loss=0.665, update\u001b[A\n", + "Epoch 69/69: 100%|▉| 6168/6182 [3:26:44<00:25, 1.81s/update, loss=0.453, update\u001b[A\n", + "Epoch 69/69: 100%|▉| 6169/6182 [3:26:47<00:26, 2.07s/update, loss=0.453, update\u001b[A\n", + "Epoch 69/69: 100%|▉| 6169/6182 [3:26:47<00:26, 2.07s/update, loss=0.559, update\u001b[A\n", + "Epoch 69/69: 100%|▉| 6170/6182 [3:26:49<00:26, 2.20s/update, loss=0.559, update\u001b[A\n", + "Epoch 69/69: 100%|▉| 6170/6182 [3:26:49<00:26, 2.20s/update, loss=0.465, update\u001b[A\n", + "Epoch 69/69: 100%|▉| 6171/6182 [3:26:51<00:22, 2.04s/update, loss=0.465, update\u001b[A\n", + "Epoch 69/69: 100%|▉| 6171/6182 [3:26:51<00:22, 2.04s/update, loss=0.433, update\u001b[A\n", + "Epoch 69/69: 100%|▉| 6172/6182 [3:26:53<00:19, 1.99s/update, loss=0.433, update\u001b[A\n", + "Epoch 69/69: 100%|▉| 6172/6182 [3:26:53<00:19, 1.99s/update, loss=0.72, update=\u001b[A\n", + "Epoch 69/69: 100%|▉| 6173/6182 [3:26:55<00:17, 1.94s/update, loss=0.72, update=\u001b[A\n", + "Epoch 69/69: 100%|▉| 6173/6182 [3:26:55<00:17, 1.94s/update, loss=0.443, update\u001b[A\n", + "Epoch 69/69: 100%|▉| 6174/6182 [3:26:56<00:15, 1.90s/update, loss=0.443, update\u001b[A\n", + "Epoch 69/69: 100%|▉| 6174/6182 [3:26:56<00:15, 1.90s/update, loss=0.478, update\u001b[A\n", + "Epoch 69/69: 100%|▉| 6175/6182 [3:26:59<00:13, 1.99s/update, loss=0.478, update\u001b[A\n", + "Epoch 69/69: 100%|▉| 6175/6182 [3:26:59<00:13, 1.99s/update, loss=0.972, update\u001b[A\n", + "Epoch 69/69: 100%|▉| 6176/6182 [3:27:01<00:13, 2.27s/update, loss=0.972, update\u001b[A\n", + "Epoch 69/69: 100%|▉| 6176/6182 [3:27:01<00:13, 2.27s/update, loss=0.398, update\u001b[A\n", + "Epoch 69/69: 100%|▉| 6177/6182 [3:27:04<00:11, 2.24s/update, loss=0.398, update\u001b[A\n", + "Epoch 69/69: 100%|▉| 6177/6182 [3:27:04<00:11, 2.24s/update, loss=0.585, update\u001b[A\n", + "Epoch 69/69: 100%|▉| 6178/6182 [3:27:05<00:08, 2.04s/update, loss=0.585, update\u001b[A\n", + "Epoch 69/69: 100%|▉| 6178/6182 [3:27:05<00:08, 2.04s/update, loss=0.515, update\u001b[A\n", + "Epoch 69/69: 100%|▉| 6179/6182 [3:27:08<00:06, 2.12s/update, loss=0.515, update\u001b[A\n", + "Epoch 69/69: 100%|▉| 6179/6182 [3:27:08<00:06, 2.12s/update, loss=0.48, update=\u001b[A\n", + "Epoch 69/69: 100%|▉| 6180/6182 [3:27:09<00:04, 2.00s/update, loss=0.48, update=\u001b[A\n", + "Epoch 69/69: 100%|▉| 6180/6182 [3:27:09<00:04, 2.00s/update, loss=0.491, update\u001b[A\n", + "Epoch 69/69: 100%|▉| 6181/6182 [3:27:12<00:02, 2.18s/update, loss=0.491, update\u001b[A\n", + "Epoch 69/69: 100%|▉| 6181/6182 [3:27:12<00:02, 2.18s/update, loss=0.526, update\u001b[A\n", + "Epoch 69/69: 100%|█| 6182/6182 [3:27:13<00:00, 1.83s/update, loss=0.526, update\u001b[A\n", + "Epoch 69/69: 100%|█| 6182/6182 [3:27:13<00:00, 1.83s/update, loss=0.438, update\u001b[ASaved last checkpoint at update 426524\n", + "Epoch 69/69: 100%|█| 6182/6182 [3:27:36<00:00, 2.01s/update, loss=0.438, update\n" + ] + } + ], + "source": [ + "# ************\n", + "!accelerate launch ./src/f5_tts/train/finetune_cli.py \\\n", + " --exp_name F5TTS_Base \\\n", + " --dataset_name vin100h-preprocessed-v2 \\\n", + " --finetune \\\n", + " --tokenizer pinyin \\\n", + " --learning_rate 1e-05 \\\n", + " --batch_size_type frame \\\n", + " --batch_size_per_gpu 3200 \\\n", + " --max_samples 64 \\\n", + " --grad_accumulation_steps 2 \\\n", + " --max_grad_norm 1 \\\n", + " --epochs 69 \\\n", + " --num_warmup_updates 2761 \\\n", + " --save_per_updates 4000 \\\n", + " --keep_last_n_checkpoints 1 \\\n", + " --last_per_updates 4000 \\\n", + " --log_samples \\\n", + " --pretrain ./ckpts/vin100h-preprocessed-v2/model_last.pt\n", + " # [--pretrain PRETRAIN\n", + " #10 6182" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### ok" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": { + "execution": { + "iopub.execute_input": "2025-06-13T17:28:25.043296Z", + "iopub.status.busy": "2025-06-13T17:28:25.042613Z", + "iopub.status.idle": "2025-06-13T17:28:25.267641Z", + "shell.execute_reply": "2025-06-13T17:28:25.266919Z", + "shell.execute_reply.started": "2025-06-13T17:28:25.043266Z" + }, + "trusted": true + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "abc\n" + ] + } + ], + "source": [ + "!echo abc" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "metadata": { + "execution": { + "iopub.execute_input": "2025-06-13T17:32:23.792639Z", + "iopub.status.busy": "2025-06-13T17:32:23.792430Z", + "iopub.status.idle": "2025-06-13T17:32:23.923708Z", + "shell.execute_reply": "2025-06-13T17:32:23.922745Z", + "shell.execute_reply.started": "2025-06-13T17:32:23.792623Z" + }, + "trusted": true + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "done\n" + ] + } + ], + "source": [ + "!echo done" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Copy and save" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": { + "execution": { + "iopub.execute_input": "2025-06-13T17:28:35.363524Z", + "iopub.status.busy": "2025-06-13T17:28:35.362844Z", + "iopub.status.idle": "2025-06-13T17:28:48.560091Z", + "shell.execute_reply": "2025-06-13T17:28:48.558954Z", + "shell.execute_reply.started": "2025-06-13T17:28:35.363495Z" + }, + "trusted": true + }, + "outputs": [], + "source": [ + "# *******************88888\n", + "saving_path = '/kaggle/working/69'\n", + "os.makedirs(saving_path, exist_ok=True)\n", + "!cp -r ./ckpts/vin100h-preprocessed-v2/model_last.pt $saving_path" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "metadata": { + "execution": { + "iopub.execute_input": "2025-06-13T17:29:03.139553Z", + "iopub.status.busy": "2025-06-13T17:29:03.139259Z", + "iopub.status.idle": "2025-06-13T17:32:23.791415Z", + "shell.execute_reply": "2025-06-13T17:32:23.790789Z", + "shell.execute_reply.started": "2025-06-13T17:29:03.139527Z" + }, + "trusted": true + }, + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "2633f42f0e504628a022a47ce1743277", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "Recovering from metadata files: 0%| | 0/1 [00:00 str:\n", + " try:\n", + " checkpoint = torch.load(checkpoint_path, weights_only=True)\n", + " print(\"Original Checkpoint Keys:\", checkpoint.keys())\n", + "\n", + " to_retain = \"ema_model_state_dict\" if save_ema else \"model_state_dict\"\n", + " try:\n", + " model_state_dict_to_retain = checkpoint[to_retain]\n", + " except KeyError:\n", + " return f\"{to_retain} not found in the checkpoint.\"\n", + "\n", + " if safetensors:\n", + " new_checkpoint_path = new_checkpoint_path.replace(\".pt\", \".safetensors\")\n", + " save_file(model_state_dict_to_retain, new_checkpoint_path)\n", + " else:\n", + " new_checkpoint_path = new_checkpoint_path.replace(\".safetensors\", \".pt\")\n", + " new_checkpoint = {\"ema_model_state_dict\": model_state_dict_to_retain}\n", + " torch.save(new_checkpoint, new_checkpoint_path)\n", + "\n", + " return f\"New checkpoint saved at: {new_checkpoint_path}\"\n", + "\n", + " except Exception as e:\n", + " return f\"An error occurred: {e}\"" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "execution": { + "iopub.execute_input": "2025-05-11T14:22:24.624318Z", + "iopub.status.busy": "2025-05-11T14:22:24.623974Z", + "iopub.status.idle": "2025-05-11T14:22:30.316195Z", + "shell.execute_reply": "2025-05-11T14:22:30.315529Z", + "shell.execute_reply.started": "2025-05-11T14:22:24.624292Z" + }, + "trusted": true + }, + "outputs": [], + "source": [ + "# Prune a checkpoint after training resize model\n", + "result = prune_checkpoint(\n", + " checkpoint_path=\"/kaggle/working/F5-TTS/ckpts/vin100h-preprocessed-v2/model_last.pt\",\n", + " new_checkpoint_path=\"/root/.cache/abc.pt\",\n", + " save_ema=False,\n", + " safetensors=False\n", + ")\n", + "print(result)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Inference" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "execution": { + "execution_failed": "2025-05-26T16:06:23.829Z" + }, + "trusted": true + }, + "outputs": [], + "source": [ + "from IPython.display import Audio\n", + "Audio(filename=\"./data/vin100h-preprocessed-v2/wavs/audio_000010.wav\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "execution": { + "iopub.execute_input": "2025-05-26T12:47:31.959152Z", + "iopub.status.busy": "2025-05-26T12:47:31.958923Z", + "iopub.status.idle": "2025-05-26T12:48:10.349264Z", + "shell.execute_reply": "2025-05-26T12:48:10.348434Z", + "shell.execute_reply.started": "2025-05-26T12:47:31.959126Z" + }, + "trusted": true + }, + "outputs": [], + "source": [ + "!python ./src/f5_tts/infer/infer_cli.py \\\n", + " --model \"vin100h-preprocessed-v2\" \\\n", + " --model_cfg \"./src/f5_tts/configs/F5TTS_Base.yaml\" \\\n", + " --ckpt_file \"./ckpts/vin100h-preprocessed-v2/model_last.pt\" \\\n", + " --vocab_file \"./data/vin100h-preprocessed-v2_pinyin/vocab.txt\" \\\n", + " --ref_audio \"./data/vin100h-preprocessed-v2/wavs/audio_000010.wav\" \\\n", + " --ref_text \"Về giá cả so với giá bán ngoài các siêu thị thì dâu trái ở đây rẻ hơn khá nhiều. Giả sử như bó rau ở siêu thị bán khoảng 2 đô la một bó thì ở đây chỉ có một đô la một bó. Có khi mua 50 bó được tặng thêm một bó nữa.\" \\\n", + " --gen_text \"Chào cả nhà cả nhà khỏe không\" \\\n", + " --output_dir \"/kaggle/working\"\n", + " # --output_file \"/content/abc.wav\"" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "execution": { + "iopub.execute_input": "2025-05-26T12:48:40.946990Z", + "iopub.status.busy": "2025-05-26T12:48:40.946348Z", + "iopub.status.idle": "2025-05-26T12:48:40.953421Z", + "shell.execute_reply": "2025-05-26T12:48:40.952760Z", + "shell.execute_reply.started": "2025-05-26T12:48:40.946967Z" + }, + "trusted": true + }, + "outputs": [], + "source": [ + "from IPython.display import Audio\n", + "Audio(filename=\"/kaggle/working/infer_cli_basic.wav\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Download" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": { + "execution": { + "iopub.execute_input": "2025-06-13T10:26:34.769075Z", + "iopub.status.busy": "2025-06-13T10:26:34.768613Z", + "iopub.status.idle": "2025-06-13T10:27:05.837772Z", + "shell.execute_reply": "2025-06-13T10:27:05.836916Z", + "shell.execute_reply.started": "2025-06-13T10:26:34.769051Z" + }, + "trusted": true + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Updated git hooks.\n", + "Git LFS initialized.\n" + ] + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "03307eb632cd40898c25188a7cd2fcac", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "Fetching 2 files: 0%| | 0/2 [00:00\")" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": { + "execution": { + "iopub.execute_input": "2025-06-17T15:01:47.138696Z", + "iopub.status.busy": "2025-06-17T15:01:47.138320Z", + "iopub.status.idle": "2025-06-17T15:01:47.142640Z", + "shell.execute_reply": "2025-06-17T15:01:47.141872Z", + "shell.execute_reply.started": "2025-06-17T15:01:47.138677Z" + }, + "trusted": true + }, + "outputs": [], + "source": [ + "os.chdir(\"/content\")" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": { + "execution": { + "iopub.execute_input": "2025-06-17T15:01:47.144207Z", + "iopub.status.busy": "2025-06-17T15:01:47.143938Z", + "iopub.status.idle": "2025-06-17T15:05:03.276239Z", + "shell.execute_reply": "2025-06-17T15:05:03.275559Z", + "shell.execute_reply.started": "2025-06-17T15:01:47.144181Z" + }, + "trusted": true + }, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Cloning into 'F5-TTS'...\n" + ] + }, + { + "data": { + "text/plain": [ + "0" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Step 1: Set Up the Environment\n", + "os.system(\"pip install -e . >/dev/null 2>&1\")\n", + "os.system(\"pip install torch==2.4.0+cu124 torchaudio==2.4.0+cu124 torchvision==0.19.0+cu124 --extra-index-url https://download.pytorch.org/whl/cu124 >/dev/null 2>&1\")\n", + "os.system(\"pip install accelerate==0.33.0 tensorboard >/dev/null 2>&1\")\n", + "if not os.path.exists(\"F5-TTS\"):\n", + " # os.system(\"git clone https://github.com/SWivid/F5-TTS.git\")\n", + " os.system(\"git clone https://github.com/danhtran2mind/F5-TTS.git\")\n", + "os.chdir(\"F5-TTS\")\n", + "os.system(\"pip install -e . >/dev/null 2>&1\")\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "execution": { + "iopub.execute_input": "2025-06-17T15:05:03.277361Z", + "iopub.status.busy": "2025-06-17T15:05:03.277007Z", + "iopub.status.idle": "2025-06-17T15:05:03.280866Z", + "shell.execute_reply": "2025-06-17T15:05:03.280113Z", + "shell.execute_reply.started": "2025-06-17T15:05:03.277341Z" + }, + "trusted": true + }, + "outputs": [], + "source": [ + "os.chdir(\"/content/F5-TTS\")\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "execution": { + "iopub.execute_input": "2025-05-10T15:59:08.329794Z", + "iopub.status.busy": "2025-05-10T15:59:08.329442Z", + "iopub.status.idle": "2025-05-10T15:59:09.362207Z", + "shell.execute_reply": "2025-05-10T15:59:09.361253Z", + "shell.execute_reply.started": "2025-05-10T15:59:08.329757Z" + }, + "trusted": true + }, + "outputs": [], + "source": [ + "import json\n", + "import os\n", + "from pathlib import Path\n", + "import shutil\n", + "import torchaudio\n", + "from datasets import load_dataset\n", + "from datasets.arrow_writer import ArrowWriter\n", + "from tqdm import tqdm\n", + "import soundfile as sf\n", + "import csv\n", + "\n", + "def save_dataset_to_local_disk(output_dir=\"./data/vin100h-preprocessed-v2\",\n", + " base_model=\"htdung167/vin100h-preprocessed-v2\",\n", + " audio_header='audio', text_header='transcription'):\n", + " \n", + " wavs_dir = os.path.join(output_dir, \"wavs\")\n", + " metadata_path = os.path.join(output_dir, \"metadata.csv\")\n", + " os.makedirs(wavs_dir, exist_ok=True)\n", + "\n", + " ds = load_dataset(base_model)['train']\n", + " metadata = []\n", + "\n", + " for idx, sample in tqdm(enumerate(ds), total=len(ds),\n", + " desc=\"Saving samples to directory\"):\n", + " audio_array = sample[audio_header]['array']\n", + " sampling_rate = sample[audio_header]['sampling_rate']\n", + " filename = f\"audio_{idx:06d}.wav\"\n", + " sf.write(os.path.join(wavs_dir, filename), audio_array, sampling_rate)\n", + " # metadata.append([f\"wavs/{filename}\", sample['preprocessed_sentence_v2']])\n", + " metadata.append([f\"wavs/{filename}\", sample[text_header]])\n", + " # metadata.append([f\"{filename}\", sample['transcription']])\n", + " \n", + " with open(metadata_path, 'w', newline='', encoding='utf-8') as f:\n", + " csv.writer(f, delimiter='|').writerows(metadata)\n", + "\n", + " print(f\"Dataset saved to {output_dir}\")\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "execution": { + "iopub.execute_input": "2025-05-10T15:59:10.399030Z", + "iopub.status.busy": "2025-05-10T15:59:10.397916Z", + "iopub.status.idle": "2025-05-10T16:10:46.269067Z", + "shell.execute_reply": "2025-05-10T16:10:46.267298Z", + "shell.execute_reply.started": "2025-05-10T15:59:10.398995Z" + }, + "trusted": true + }, + "outputs": [], + "source": [ + "output_dir = \"./data/vin100h-preprocessed-v2\"\n", + "tokenizer_type = \"pinyin\"\n", + "\n", + "save_dataset_to_local_disk(output_dir=output_dir,\n", + " base_model=\"htdung167/vin100h-preprocessed-v2\",\n", + " text_header=\"preprocessed_sentence_v2\"\n", + " )" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "execution": { + "iopub.execute_input": "2025-05-10T16:10:46.273403Z", + "iopub.status.busy": "2025-05-10T16:10:46.272176Z", + "iopub.status.idle": "2025-05-10T17:15:19.405258Z", + "shell.execute_reply": "2025-05-10T17:15:19.402002Z", + "shell.execute_reply.started": "2025-05-10T16:10:46.273366Z" + }, + "trusted": true + }, + "outputs": [], + "source": [ + "!python ./src/f5_tts/train/datasets/prepare_csv_wavs.py \\\n", + " \"./data/vin100h-preprocessed-v2\" \\\n", + " \"./data/vin100h-preprocessed-v2_pinyin\" \\\n", + " --workers 4 # Sets the number of parallel processes for preprocessing." + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "metadata": { + "execution": { + "iopub.execute_input": "2025-06-17T15:20:02.239561Z", + "iopub.status.busy": "2025-06-17T15:20:02.238766Z", + "iopub.status.idle": "2025-06-17T15:20:02.245371Z", + "shell.execute_reply": "2025-06-17T15:20:02.244794Z", + "shell.execute_reply.started": "2025-06-17T15:20:02.239531Z" + }, + "trusted": true + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Writing ./src/f5_tts/configs/vi-fine-tuned-f5-tts.yaml\n" + ] + } + ], + "source": [ + "%%writefile ./src/f5_tts/configs/vi-fine-tuned-f5-tts.yaml\n", + "hydra:\n", + " run:\n", + " dir: ckpts/${model.name}_${model.mel_spec.mel_spec_type}_${model.tokenizer}_${datasets.name}/${now:%Y-%m-%d}/${now:%H-%M-%S}\n", + "\n", + "datasets:\n", + " name: vin100h-preprocessed-v2 # dataset name\n", + " batch_size_per_gpu: 3200 # 1 GPUs, 1 * 3200 = 3200\n", + " batch_size_type: frame # frame | sample\n", + " max_samples: 64 # max sequences per batch if use frame-wise batch_size. we set 32 for small models, 64 for base models\n", + " num_workers: 4\n", + "\n", + "optim:\n", + " epochs: 80\n", + " learning_rate: 1e-5\n", + " num_warmup_updates: 2761 # warmup updates\n", + " grad_accumulation_steps: 2 # note: updates = steps / grad_accumulation_steps\n", + " max_grad_norm: 1.0 # gradient clipping\n", + " bnb_optimizer: False # use bnb 8bit AdamW optimizer or not\n", + "\n", + "model:\n", + " name: vi_fine_tuned_t5_tts # model name\n", + " tokenizer: pinyin # tokenizer type\n", + " tokenizer_path: null # if 'custom' tokenizer, define the path want to use (should be vocab.txt)\n", + " backbone: DiT\n", + " arch:\n", + " dim: 1024\n", + " depth: 22\n", + " heads: 16\n", + " ff_mult: 2\n", + " text_dim: 512\n", + " text_mask_padding: False\n", + " conv_layers: 4\n", + " pe_attn_head: 1\n", + " checkpoint_activations: False # recompute activations and save memory for extra compute\n", + " mel_spec:\n", + " target_sample_rate: 24000\n", + " n_mel_channels: 100\n", + " hop_length: 256\n", + " win_length: 1024\n", + " n_fft: 1024\n", + " mel_spec_type: vocos # vocos | bigvgan\n", + " vocoder:\n", + " is_local: False # use local offline ckpt or not\n", + " local_path: null # local vocoder path\n", + "\n", + "ckpts:\n", + " logger: null # wandb | tensorboard | null\n", + " log_samples: True # infer random sample per save checkpoint. wip, normal to fail with extra long samples\n", + " save_per_updates: 4000 # save checkpoint per updates\n", + " keep_last_n_checkpoints: 1 # -1 to keep all, 0 to not save intermediate, > 0 to keep last N checkpoints\n", + " last_per_updates: 4000 # save last checkpoint per updates\n", + " save_dir: ckpts/${model.name}_${model.mel_spec.mel_spec_type}_${model.tokenizer}_${datasets.name}" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": { + "execution": { + "iopub.execute_input": "2025-06-17T15:05:54.147828Z", + "iopub.status.busy": "2025-06-17T15:05:54.147535Z", + "iopub.status.idle": "2025-06-17T15:06:09.542218Z", + "shell.execute_reply": "2025-06-17T15:06:09.541348Z", + "shell.execute_reply.started": "2025-06-17T15:05:54.147805Z" + }, + "trusted": true + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "accelerate configuration saved at /root/.cache/huggingface/accelerate/default_config.yaml\n" + ] + } + ], + "source": [ + "!accelerate config default" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "execution": { + "iopub.execute_input": "2025-06-17T15:16:30.232115Z", + "iopub.status.busy": "2025-06-17T15:16:30.231283Z", + "iopub.status.idle": "2025-06-17T15:18:25.550165Z", + "shell.execute_reply": "2025-06-17T15:18:25.548630Z", + "shell.execute_reply.started": "2025-06-17T15:16:30.232085Z" + }, + "trusted": true + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "copy checkpoint for finetune\n", + "\n", + "vocab : 2545\n", + "\n", + "vocoder : vocos\n", + "Using logger: None\n", + "Gradient accumulation checkpointing with per_updates now, old logic per_steps used with before f992c4e\n", + "Loading dataset ...\n", + "2025-06-17 15:17:40.763073: E external/local_xla/xla/stream_executor/cuda/cuda_fft.cc:477] Unable to register cuFFT factory: Attempting to register factory for plugin cuFFT when one has already been registered\n", + "WARNING: All log messages before absl::InitializeLog() is called are written to STDERR\n", + "E0000 00:00:1750173460.969428 249 cuda_dnn.cc:8310] Unable to register cuDNN factory: Attempting to register factory for plugin cuDNN when one has already been registered\n", + "E0000 00:00:1750173461.025851 249 cuda_blas.cc:1418] Unable to register cuBLAS factory: Attempting to register factory for plugin cuBLAS when one has already been registered\n", + "Download Vocos from huggingface charactr/vocos-mel-24khz\n", + "config.yaml: 100%|█████████████████████████████| 461/461 [00:00<00:00, 3.57MB/s]\n", + "Xet Storage is enabled for this repo, but the 'hf_xet' package is not installed. Falling back to regular HTTP download. For better performance, install the package with: `pip install huggingface_hub[hf_xet]` or `pip install hf_xet`\n", + "pytorch_model.bin: 100%|████████████████████| 54.4M/54.4M [00:00<00:00, 261MB/s]\n", + "Sorting with sampler... if slow, check whether dataset is provided with duration\n", + "Creating dynamic batches with 3200 audio frames per gpu: 100%|█| 56400/56400 [00\n", + "/usr/local/lib/python3.11/dist-packages/torch/utils/data/dataloader.py:557: UserWarning: This DataLoader will create 16 worker processes in total. Our suggested max number of worker in current system is 4, which is smaller than what this DataLoader is going to create. Please be aware that excessive worker creation might get DataLoader running slow or even freeze, lower the worker number to avoid potential slowness/freeze if necessary.\n", + " warnings.warn(_create_warning_msg(\n", + "Epoch 79/80: 0%| | 5/6182 [00:13<5:10:52, 3.02s/update, loss=0.843, update=49^C\n" + ] + } + ], + "source": [ + "!accelerate launch ./src/f5_tts/train/finetune_cli.py \\\n", + " --exp_name F5TTS_Base \\\n", + " --dataset_name vin100h-preprocessed-v2 \\\n", + " --finetune \\\n", + " --tokenizer pinyin \\\n", + " --learning_rate 1e-05 \\\n", + " --batch_size_type frame \\\n", + " --batch_size_per_gpu 3200 \\\n", + " --max_samples 64 \\\n", + " --grad_accumulation_steps 2 \\\n", + " --max_grad_norm 1 \\\n", + " --epochs 80 \\\n", + " --num_warmup_updates 2761 \\\n", + " --save_per_updates 4000 \\\n", + " --keep_last_n_checkpoints 1 \\\n", + " --last_per_updates 4000 \\\n", + " --log_samples \\\n", + " --pretrain ./ckpts/vin100h-preprocessed-v2/model_last.pt" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Prune Checkpoint" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "execution": { + "iopub.execute_input": "2025-05-11T14:11:57.837831Z", + "iopub.status.busy": "2025-05-11T14:11:57.837476Z", + "iopub.status.idle": "2025-05-11T14:11:57.844498Z", + "shell.execute_reply": "2025-05-11T14:11:57.843701Z", + "shell.execute_reply.started": "2025-05-11T14:11:57.837803Z" + }, + "trusted": true + }, + "outputs": [], + "source": [ + "import torch\n", + "\n", + "def prune_checkpoint(checkpoint_path: str, new_checkpoint_path: str, save_ema: bool, safetensors: bool) -> str:\n", + " try:\n", + " checkpoint = torch.load(checkpoint_path, weights_only=True)\n", + " print(\"Original Checkpoint Keys:\", checkpoint.keys())\n", + "\n", + " to_retain = \"ema_model_state_dict\" if save_ema else \"model_state_dict\"\n", + " try:\n", + " model_state_dict_to_retain = checkpoint[to_retain]\n", + " except KeyError:\n", + " return f\"{to_retain} not found in the checkpoint.\"\n", + "\n", + " if safetensors:\n", + " new_checkpoint_path = new_checkpoint_path.replace(\".pt\", \".safetensors\")\n", + " save_file(model_state_dict_to_retain, new_checkpoint_path)\n", + " else:\n", + " new_checkpoint_path = new_checkpoint_path.replace(\".safetensors\", \".pt\")\n", + " new_checkpoint = {\"ema_model_state_dict\": model_state_dict_to_retain}\n", + " torch.save(new_checkpoint, new_checkpoint_path)\n", + "\n", + " return f\"New checkpoint saved at: {new_checkpoint_path}\"\n", + "\n", + " except Exception as e:\n", + " return f\"An error occurred: {e}\"" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "execution": { + "iopub.execute_input": "2025-05-11T14:22:24.624318Z", + "iopub.status.busy": "2025-05-11T14:22:24.623974Z", + "iopub.status.idle": "2025-05-11T14:22:30.316195Z", + "shell.execute_reply": "2025-05-11T14:22:30.315529Z", + "shell.execute_reply.started": "2025-05-11T14:22:24.624292Z" + }, + "trusted": true + }, + "outputs": [], + "source": [ + "# Prune a checkpoint after training resize model\n", + "result = prune_checkpoint(\n", + " checkpoint_path=\"/kaggle/working/F5-TTS/ckpts/vin100h-preprocessed-v2/model_last.pt\",\n", + " new_checkpoint_path=\"/root/.cache/abc.pt\",\n", + " save_ema=False,\n", + " safetensors=False\n", + ")\n", + "print(result)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Inference" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "execution": { + "iopub.execute_input": "2025-06-04T09:45:21.012950Z", + "iopub.status.busy": "2025-06-04T09:45:21.012568Z", + "iopub.status.idle": "2025-06-04T09:45:21.032225Z", + "shell.execute_reply": "2025-06-04T09:45:21.031171Z", + "shell.execute_reply.started": "2025-06-04T09:45:21.012924Z" + }, + "trusted": true + }, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + " \n", + " " + ], + "text/plain": [ + "" + ] + }, + "execution_count": 20, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from IPython.display import Audio\n", + "\n", + "# Path to your audio file\n", + "audio_path = './data/vin100h-preprocessed-v2/wavs/audio_000010.wav'\n", + "# Display and play the audio\n", + "Audio(audio_path)" + ] + }, + { + "cell_type": "code", + "execution_count": 27, + "metadata": { + "execution": { + "iopub.execute_input": "2025-06-17T15:27:26.150679Z", + "iopub.status.busy": "2025-06-17T15:27:26.150330Z", + "iopub.status.idle": "2025-06-17T15:28:18.529875Z", + "shell.execute_reply": "2025-06-17T15:28:18.528858Z", + "shell.execute_reply.started": "2025-06-17T15:27:26.150650Z" + }, + "trusted": true + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "2025-06-17 15:27:38.164110: E external/local_xla/xla/stream_executor/cuda/cuda_fft.cc:477] Unable to register cuFFT factory: Attempting to register factory for plugin cuFFT when one has already been registered\n", + "WARNING: All log messages before absl::InitializeLog() is called are written to STDERR\n", + "E0000 00:00:1750174058.189595 391 cuda_dnn.cc:8310] Unable to register cuDNN factory: Attempting to register factory for plugin cuDNN when one has already been registered\n", + "E0000 00:00:1750174058.196516 391 cuda_blas.cc:1418] Unable to register cuBLAS factory: Attempting to register factory for plugin cuBLAS when one has already been registered\n", + "Download Vocos from huggingface charactr/vocos-mel-24khz\n", + "Using vin100h-preprocessed-v2...\n", + "\n", + "vocab : ./data/vin100h-preprocessed-v2_pinyin/vocab.txt\n", + "token : custom\n", + "model : ./ckpts/vin100h-preprocessed-v2/model_last.pt \n", + "\n", + "Voice: main\n", + "ref_audio ./data/vin100h-preprocessed-v2/wavs/audio_000010.wav\n", + "Converting audio...\n", + "Audio is over 12s, clipping short. (2)\n", + "Using custom reference text...\n", + "\n", + "ref_text Về giá cả so với giá bán ngoài các siêu thị thì dâu trái ở đây rẻ hơn khá nhiều. Giả sử như bó rau ở siêu thị bán khoảng 2 đô la một bó thì ở đây chỉ có một đô la một bó. Có khi mua 50 bó được tặng thêm một bó nữa. \n", + "ref_audio_ /tmp/tmpjucisns9.wav \n", + "\n", + "\n", + "No voice tag found, using main.\n", + "Voice: main\n", + "gen_text 0 Tuy nhiên đôi khi vẫn có những trường hợp trục lợi trợ cấp khi không khai báo đầy đủ về người có nghĩa vụ chu cấp, cũng như những thay đổi về thu nhập và tài sản của mình.\n", + "\n", + "\n", + "Generating audio in 1 batches...\n", + "100%|█████████████████████████████████████████████| 1/1 [00:16<00:00, 16.82s/it]\n", + "/kaggle/working/infer_cli_basic.wav\n", + "52.37339425086975\n" + ] + } + ], + "source": [ + "import time\n", + "\n", + "t1 = time.time()\n", + "!python ./src/f5_tts/infer/infer_cli.py \\\n", + " --model \"vin100h-preprocessed-v2\" \\\n", + " --model_cfg \"./src/f5_tts/configs/vi-fine-tuned-f5-tts.yaml\" \\\n", + " --ckpt_file \"./ckpts/vin100h-preprocessed-v2/model_last.pt\" \\\n", + " --vocab_file \"./data/vin100h-preprocessed-v2_pinyin/vocab.txt\" \\\n", + " --ref_audio \"./data/vin100h-preprocessed-v2/wavs/audio_000010.wav\" \\\n", + " --ref_text \"Về giá cả so với giá bán ngoài các siêu thị thì dâu trái ở đây rẻ hơn khá nhiều. Giả sử như bó rau ở siêu thị bán khoảng 2 đô la một bó thì ở đây chỉ có một đô la một bó. Có khi mua 50 bó được tặng thêm một bó nữa.\" \\\n", + " --gen_text \"Tuy nhiên đôi khi vẫn có những trường hợp trục lợi trợ cấp khi không khai báo đầy đủ về người có nghĩa vụ chu cấp, cũng như những thay đổi về thu nhập và tài sản của mình.\" \\\n", + " --output_dir \"/kaggle/working/\"\n", + " # --output_file \"/content/abc.wav\"\n", + "\n", + "print(time.time() - t1)" + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "metadata": { + "execution": { + "iopub.execute_input": "2025-06-17T10:23:52.564882Z", + "iopub.status.busy": "2025-06-17T10:23:52.564411Z", + "iopub.status.idle": "2025-06-17T10:24:36.841824Z", + "shell.execute_reply": "2025-06-17T10:24:36.840934Z", + "shell.execute_reply.started": "2025-06-17T10:23:52.564858Z" + }, + "trusted": true + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "2025-06-17 10:24:02.873808: E external/local_xla/xla/stream_executor/cuda/cuda_fft.cc:477] Unable to register cuFFT factory: Attempting to register factory for plugin cuFFT when one has already been registered\n", + "WARNING: All log messages before absl::InitializeLog() is called are written to STDERR\n", + "E0000 00:00:1750155842.897993 500 cuda_dnn.cc:8310] Unable to register cuDNN factory: Attempting to register factory for plugin cuDNN when one has already been registered\n", + "E0000 00:00:1750155842.905125 500 cuda_blas.cc:1418] Unable to register cuBLAS factory: Attempting to register factory for plugin cuBLAS when one has already been registered\n", + "Download Vocos from huggingface charactr/vocos-mel-24khz\n", + "Using vin100h-preprocessed-v2...\n", + "\n", + "vocab : ./data/vin100h-preprocessed-v2_pinyin/vocab.txt\n", + "token : custom\n", + "model : ./ckpts/vin100h-preprocessed-v2/model_last.pt \n", + "\n", + "Voice: main\n", + "ref_audio ./data/vin100h-preprocessed-v2/wavs/audio_000010.wav\n", + "Converting audio...\n", + "Audio is over 12s, clipping short. (2)\n", + "Using custom reference text...\n", + "\n", + "ref_text Về giá cả so với giá bán ngoài các siêu thị thì dâu trái ở đây rẻ hơn khá nhiều. Giả sử như bó rau ở siêu thị bán khoảng 2 đô la một bó thì ở đây chỉ có một đô la một bó. Có khi mua 50 bó được tặng thêm một bó nữa. \n", + "ref_audio_ /tmp/tmp6_z8vr7d.wav \n", + "\n", + "\n", + "No voice tag found, using main.\n", + "Voice: main\n", + "gen_text 0 Tuy nhiên đôi khi vẫn có những trường hợp trục lợi trợ cấp khi không khai báo đầy đủ về người có nghĩa vụ chu cấp, cũng như những thay đổi về thu nhập và tài sản của mình.\n", + "\n", + "\n", + "Generating audio in 1 batches...\n", + "100%|█████████████████████████████████████████████| 1/1 [00:14<00:00, 14.86s/it]\n", + "/kaggle/working/infer_cli_basic.wav\n", + "44.271546602249146\n" + ] + } + ], + "source": [ + "import time\n", + "\n", + "t1 = time.time()\n", + "!python ./src/f5_tts/infer/infer_cli.py \\\n", + " --model \"vin100h-preprocessed-v2\" \\\n", + " --model_cfg \"./src/f5_tts/configs/F5TTS_Base.yaml\" \\\n", + " --ckpt_file \"./ckpts/vin100h-preprocessed-v2/model_last.pt\" \\\n", + " --vocab_file \"./data/vin100h-preprocessed-v2_pinyin/vocab.txt\" \\\n", + " --ref_audio \"./data/vin100h-preprocessed-v2/wavs/audio_000010.wav\" \\\n", + " --ref_text \"Về giá cả so với giá bán ngoài các siêu thị thì dâu trái ở đây rẻ hơn khá nhiều. Giả sử như bó rau ở siêu thị bán khoảng 2 đô la một bó thì ở đây chỉ có một đô la một bó. Có khi mua 50 bó được tặng thêm một bó nữa.\" \\\n", + " --gen_text \"Tuy nhiên đôi khi vẫn có những trường hợp trục lợi trợ cấp khi không khai báo đầy đủ về người có nghĩa vụ chu cấp, cũng như những thay đổi về thu nhập và tài sản của mình.\" \\\n", + " --output_dir \"/kaggle/working/\"\n", + " # --output_file \"/content/abc.wav\"\n", + "\n", + "print(time.time() - t1)" + ] + }, + { + "cell_type": "code", + "execution_count": 28, + "metadata": { + "execution": { + "iopub.execute_input": "2025-06-17T15:28:18.532293Z", + "iopub.status.busy": "2025-06-17T15:28:18.531632Z", + "iopub.status.idle": "2025-06-17T15:28:18.575767Z", + "shell.execute_reply": "2025-06-17T15:28:18.574975Z", + "shell.execute_reply.started": "2025-06-17T15:28:18.532267Z" + }, + "trusted": true + }, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + " \n", + " " + ], + "text/plain": [ + "" + ] + }, + "execution_count": 28, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from IPython.display import Audio\n", + "\n", + "# Path to your audio file\n", + "audio_path = '/kaggle/working/infer_cli_basic.wav'\n", + "\n", + "# Display and play the audio\n", + "Audio(audio_path)" + ] + } + ], + "metadata": { + "kaggle": { + "accelerator": "gpu", + "dataSources": [ + { + "sourceId": 245908236, + "sourceType": "kernelVersion" + } + ], + "dockerImageVersionId": 31012, + "isGpuEnabled": true, + "isInternetEnabled": true, + "language": "python", + "sourceType": "notebook" + }, + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.11" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/requirements/.gitkeep b/requirements/.gitkeep new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/requirements/requirements.txt b/requirements/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..3b1e82a9d924df7bdef8ea3e5b2cedf0e6c9b8dc --- /dev/null +++ b/requirements/requirements.txt @@ -0,0 +1,36 @@ +accelerate>=0.33.0,!=1.7.0 +bitsandbytes>0.37.0 +cached_path +click +datasets>=3.5.0,<4.0.0 +ema_pytorch>=0.5.2 +gradio>=3.45.2 +hydra-core>=1.3.0 +jieba +librosa +matplotlib +numpy<=1.26.4 +pydantic<=2.10.6 +pydub +pypinyin +safetensors +soundfile +tomli +torch>=2.4.0 +torchaudio>=2.4.0 +torchvision>=0.19.0 +torchcodec>=0.3.0 +torchdiffeq +tqdm>=4.65.0 +transformers +transformers_stream_generator +unidecode +vocos +wandb +x_transformers>=1.31.14 +faster_whisper==0.10.1 +funasr +jiwer +modelscope +zhconv +zhon \ No newline at end of file diff --git a/requirements/requirements_compatible.txt b/requirements/requirements_compatible.txt new file mode 100644 index 0000000000000000000000000000000000000000..67adde79d500c8994ababf9b8d551fd2e8885061 --- /dev/null +++ b/requirements/requirements_compatible.txt @@ -0,0 +1,29 @@ +accelerate==0.33.0 +bitsandbytes==0.46.1 +cached_path==1.7.3 +click==8.1.8 +datasets==3.5.0 +ema_pytorch==0.7.7 +gradio==5.39.0 +hydra-core==1.3.2 +jieba==0.42.1 +librosa==0.10.2.post1 +matplotlib==3.7.5 +numpy==1.26.4 +pydantic==2.10.6 +pydub==0.25.1 +pypinyin==0.55.0 +safetensors==0.5.2 +soundfile==0.13.1 +tomli==2.2.1 +torch==2.4.0 +torchaudio==2.4.0 +torchcodec==0.4.0 +torchdiffeq==0.2.5 +tqdm==4.67.1 +transformers==4.51.1 +transformers_stream_generator==0.0.5 +unidecode==1.4.0 +vocos==0.1.0 +wandb==0.19.6 +x_transformers==2.5.6 \ No newline at end of file diff --git a/scripts/.gitkeep b/scripts/.gitkeep new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/scripts/download_ckpts.py b/scripts/download_ckpts.py new file mode 100644 index 0000000000000000000000000000000000000000..6676bf67d0887c44f4f2706034d38bb7e6280859 --- /dev/null +++ b/scripts/download_ckpts.py @@ -0,0 +1,65 @@ +from huggingface_hub import snapshot_download +import os +import argparse + +def download_ckpts(repo_id, local_dir, folder_name=None, pruning_model=False): + # Ensure the local directory exists + os.makedirs(local_dir, exist_ok=True) + + # Initialize allow_patterns + allow_patterns = None + + if pruning_model and repo_id == "danhtran2mind/Vi-F5-TTS": + # Download only specific files when pruning_model is enabled + allow_patterns = [ + "Vi_F5_TTS_ckpts/pruning_model.pt", + ".gitattributes", + "README.md", + "vi-fine-tuned-f5-tts.yaml", + "vocab.txt" + ] + print(f"Downloading only {', '.join(allow_patterns)} from {repo_id}") + elif folder_name: + # Download only the specific folder + allow_patterns = [f"{folder_name}/*"] + print(f"Downloading {folder_name} from {repo_id}") + else: + # Download entire repository + print(f"Downloading entire repository {repo_id}") + + # Perform the download + snapshot_download( + repo_id=repo_id, + allow_patterns=allow_patterns, + local_dir=local_dir, + local_dir_use_symlinks=False, # Ensure files are copied, not symlinked + ) + + # Print completion message + if pruning_model: + print(f"Downloaded specified files from {repo_id} to {local_dir}") + elif folder_name: + print(f"Downloaded {folder_name} from {repo_id} to {local_dir}") + else: + print(f"Downloaded entire repository {repo_id} to {local_dir}") + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description="Download model checkpoints from HuggingFace") + parser.add_argument("--repo_id", type=str, default="danhtran2mind/Vi-F5-TTS", + help="HuggingFace repository ID") + parser.add_argument("--local_dir", type=str, default="./ckpts", + help="Local directory to save checkpoints") + parser.add_argument("--folder_name", type=str, default="Vi_F5_TTS_ckpts", + help="Specific folder to download (optional, ignored if --pruning_model is used)") + parser.add_argument("--pruning_model", action="store_true", + help="Download only Vi_F5_TTS_ckpts/pruning_model.pt, .gitattributes, README.md, vi-fine-tuned-f5-tts.yaml, and vocab.txt from danhtran2mind/Vi-F5-TTS") + + args = parser.parse_args() + + # Override folder_name for default repo, unless pruning_model is enabled + if args.repo_id == "danhtran2mind/Vi-F5-TTS" and not args.folder_name and not args.pruning_model: + folder_name = "Vi_F5_TTS_ckpts" + else: + folder_name = args.folder_name + + download_ckpts(args.repo_id, args.local_dir, folder_name, args.pruning_model) \ No newline at end of file diff --git a/scripts/old-download_ckpts.py b/scripts/old-download_ckpts.py new file mode 100644 index 0000000000000000000000000000000000000000..35f9bb39bb2a17cb9bf1bde311b56eae3a477654 --- /dev/null +++ b/scripts/old-download_ckpts.py @@ -0,0 +1,47 @@ +from huggingface_hub import snapshot_download +import os +import argparse + +def download_ckpts(repo_id, local_dir, folder_name=None): + # Ensure the local directory exists + os.makedirs(local_dir, exist_ok=True) + + if folder_name: + # Download only the specific folder + snapshot_download( + repo_id=repo_id, + allow_patterns=[f"{folder_name}/*"], # Download only files in this folder + local_dir=local_dir, + local_dir_use_symlinks=False, # Ensure files are copied, not symlinked + ) + print(f"Downloaded {folder_name} from {repo_id} to {local_dir}") + else: + # Download entire repository + snapshot_download( + repo_id=repo_id, + local_dir=local_dir, + local_dir_use_symlinks=False, # Ensure files are copied, not symlinked + ) + print(f"Downloaded entire repository {repo_id} to {local_dir}") + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description="Download model checkpoints from HuggingFace") + parser.add_argument("--repo_id", type=str, default="SWivid/F5-TTS", + help="HuggingFace repository ID") + parser.add_argument("--local_dir", type=str, default="./ckpts", + help="Local directory to save checkpoints") + parser.add_argument("--folder_name", type=str, default="F5TTS_v1_Base_no_zero_init", + help="Specific folder to download (optional)") + parser.add_argument("--download_all", action="store_true", + help="Download entire repository instead of specific folder") + + args = parser.parse_args() + + # If download_all is specified, don't use folder filtering + folder_name = args.folder_name if not args.download_all else None + + # Override folder_name for default repo + if args.repo_id == "SWivid/F5-TTS" and not args.download_all and not args.folder_name: + folder_name = "F5TTS_v1_Base_no_zero_init" + + download_ckpts(args.repo_id, args.local_dir, folder_name) diff --git a/scripts/old-process_dataset.py b/scripts/old-process_dataset.py new file mode 100644 index 0000000000000000000000000000000000000000..b1e000a40575368791bf6ca02e3734c8462810b3 --- /dev/null +++ b/scripts/old-process_dataset.py @@ -0,0 +1,226 @@ +# import json +# import os +# from pathlib import Path +# import shutil +# import torchaudio +# from datasets import load_dataset +# from datasets.arrow_writer import ArrowWriter +# from tqdm import tqdm +# import soundfile as sf +# import csv + +# def save_dataset_to_local_disk(output_dir="./data/vin100h-preprocessed-v2", +# base_model="htdung167/vin100h-preprocessed-v2", +# audio_header='audio', text_header='transcription'): + +# wavs_dir = os.path.join(output_dir, "wavs") +# metadata_path = os.path.join(output_dir, "metadata.csv") +# os.makedirs(wavs_dir, exist_ok=True) + +# ds = load_dataset(base_model)['train'] +# metadata = [] + +# for idx, sample in tqdm(enumerate(ds), total=len(ds), +# desc="Saving samples to directory"): +# audio_array = sample[audio_header]['array'] +# sampling_rate = sample[audio_header]['sampling_rate'] +# filename = f"audio_{idx:06d}.wav" +# sf.write(os.path.join(wavs_dir, filename), audio_array, sampling_rate) +# metadata.append([f"wavs/{filename}", sample[text_header]]) + +# with open(metadata_path, 'w', newline='', encoding='utf-8') as f: +# csv.writer(f, delimiter='|').writerows(metadata) +# print(f"Dataset saved to {output_dir}") + + +# # !python ./src/f5_tts/train/datasets/prepare_csv_wavs.py \ +# # "./data/vin100h-preprocessed-v2" \ +# # "./data/vin100h-preprocessed-v2_pinyin" \ +# # --workers 4 # Sets the number of parallel processes for preprocessing. + +# # if __name__ == "__main__": +# # Define the output directory and tokenizer type +# output_dir = "./data/vin100h-preprocessed-v2" +# # tokenizer_type = "pinyin" + +# save_dataset_to_local_disk(output_dir=output_dir, +# base_model="htdung167/vin100h-preprocessed-v2", +# text_header="preprocessed_sentence_v2" +# ) + + + +# ############# + +# import subprocess +# import argparse + +# def run_preprocess(input_dir, output_dir, workers): +# command = [ +# "python", "./src/f5_tts/train/datasets/prepare_csv_wavs.py", +# input_dir, +# output_dir, +# "--workers", str(workers) +# ] +# process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True) +# stdout, stderr = process.communicate() + +# if process.returncode == 0: +# print("Preprocessing completed successfully.") +# print(stdout) +# else: +# print("Error during preprocessing:") +# print(stderr) + +# if __name__ == "__main__": +# parser = argparse.ArgumentParser(description="Run preprocessing script for dataset.") +# parser.add_argument("input_dir", type=str, help="Input directory for preprocessing") +# parser.add_argument("output_dir", type=str, help="Output directory for processed data") +# parser.add_argument("--workers", type=int, default=4, help="Number of parallel processes") + +# args = parser.parse_args() +# run_preprocess(args.input_dir, args.output_dir, args.workers) + +######################################3 +# prepare_dataset.py + +import json +import os +from pathlib import Path +import shutil +import torchaudio +from datasets import load_dataset +from datasets.arrow_writer import ArrowWriter +from tqdm import tqdm +import soundfile as sf +import csv +import subprocess +import argparse + +def save_dataset_to_local_disk(output_dir, base_model, audio_header, text_header): + """ + Saves a dataset to a local directory. + + Args: + - output_dir (str): The directory to save the dataset to. + - base_model (str): The base model to load the dataset from. + - audio_header (str): The header for the audio data in the dataset. + - text_header (str): The header for the text data in the dataset. + """ + wavs_dir = os.path.join(output_dir, "wavs") + metadata_path = os.path.join(output_dir, "metadata.csv") + os.makedirs(wavs_dir, exist_ok=True) + + ds = load_dataset(base_model)['train'] + metadata = [] + + for idx, sample in tqdm(enumerate(ds), total=len(ds), + desc="Saving samples to directory"): + audio_array = sample[audio_header]['array'] + sampling_rate = sample[audio_header]['sampling_rate'] + filename = f"audio_{idx:06d}.wav" + sf.write(os.path.join(wavs_dir, filename), audio_array, sampling_rate) + metadata.append([f"wavs/{filename}", sample[text_header]]) + + with open(metadata_path, 'w', newline='', encoding='utf-8') as f: + csv.writer(f, delimiter='|').writerows(metadata) + print(f"Dataset saved to {output_dir}") + + +# def run_preprocess(input_dir, output_dir, workers): +# """ +# Runs the preprocessing script for the dataset. + +# Args: +# - input_dir (str): The input directory for preprocessing. +# - output_dir (str): The output directory for processed data. +# - workers (int): The number of parallel processes. +# """ +# command = [ +# "python", "./src/f5_tts/train/datasets/prepare_csv_wavs.py", +# input_dir, +# output_dir, +# "--workers", str(workers) +# ] +# process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True) +# stdout, stderr = process.communicate() + +# if process.returncode == 0: +# print("Preprocessing completed successfully.") +# print(stdout) +# else: +# print("Error during preprocessing:") +# print(stderr) + +def run_preprocess(input_dir, output_dir, workers): + command = [ + "python", "./src/f5_tts/train/datasets/prepare_csv_wavs.py", + input_dir, + output_dir, + "--workers", str(workers) + ] + process = subprocess.Popen( + command, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True, + bufsize=1, # Line buffered + universal_newlines=True + ) + + # Real-time output for stdout and stderr + while True: + stdout_line = process.stdout.readline() + stderr_line = process.stderr.readline() + + if stdout_line: + print(stdout_line, end='', flush=True) + if stderr_line: + print(stderr_line, end='', flush=True, file=sys.stderr) + + if process.poll() is not None: + break + + # Capture any remaining output + stdout, stderr = process.communicate() + if stdout: + print(stdout, end='', flush=True) + if stderr: + print(stderr, end='', flush=True, file=sys.stderr) + + if process.returncode == 0: + print("\nPreprocessing completed successfully.") + else: + print("\nError during preprocessing.", file=sys.stderr) + + +if __name__ == "__main__": + # Set up argument parsing + parser = argparse.ArgumentParser(description="Prepare dataset for training.") + subparsers = parser.add_subparsers(dest="command") + + # Subcommand to save dataset to local disk + save_parser = subparsers.add_parser("save", help="Save dataset to local disk") + save_parser.add_argument("--output_dir", type=str, default="./data/vin100h-preprocessed-v2", help="Output directory") + save_parser.add_argument("--base_model", type=str, default="htdung167/vin100h-preprocessed-v2", help="Base model") + save_parser.add_argument("--audio_header", type=str, default="audio", help="Audio header") + save_parser.add_argument("--text_header", type=str, default="preprocessed_sentence_v2", help="Text header") + + # Subcommand to run preprocessing + preprocess_parser = subparsers.add_parser("preprocess", help="Run preprocessing script") + preprocess_parser.add_argument("--prepare_csv_input_dir", type=str, + default="./data/vin100h-preprocessed-v2", + help="Input directory for preprocessing") + preprocess_parser.add_argument("--prepare_csv_output_dir", type=str, + default="./data/vin100h-preprocessed-v2_pinyin", + help="Output directory for processed data") + preprocess_parser.add_argument("--workers", type=int, default=4, help="Number of parallel processes") + + args = parser.parse_args() + + if args.command == "save": + save_dataset_to_local_disk(args.output_dir, args.base_model, args.audio_header, args.text_header) + elif args.command == "preprocess": + run_preprocess(args.prepare_csv_input_dir, args.prepare_csv_output_dir, args.workers) + else: + parser.print_help() diff --git a/scripts/process_dataset.py b/scripts/process_dataset.py new file mode 100644 index 0000000000000000000000000000000000000000..069b9125578a3bdb69239130ad9f97a42274ea14 --- /dev/null +++ b/scripts/process_dataset.py @@ -0,0 +1,137 @@ +import json +import os +import sys +from pathlib import Path +import shutil +import torchaudio +from datasets import load_dataset +from datasets.arrow_writer import ArrowWriter +from tqdm import tqdm +import soundfile as sf +import csv +import subprocess +import argparse + +def save_dataset_to_local_disk(output_dir, base_model, audio_header, text_header): + """ + Saves a dataset to a local directory. + + Args: + output_dir (str): The directory to save the dataset to. + base_model (str): The base model to load the dataset from. + audio_header (str): The header for the audio data in the dataset. + text_header (str): The header for the text data in the dataset. + """ + wavs_dir = os.path.join(output_dir, "wavs") + metadata_path = os.path.join(output_dir, "metadata.csv") + os.makedirs(wavs_dir, exist_ok=True) + + try: + ds = load_dataset(base_model)['train'] + except Exception as e: + print(f"Error loading dataset: {e}", file=sys.stderr) + return + + metadata = [] + for idx, sample in tqdm(enumerate(ds), total=len(ds), desc="Saving samples to directory"): + try: + audio_array = sample[audio_header]['array'] + sampling_rate = sample[audio_header]['sampling_rate'] + filename = f"audio_{idx:06d}.wav" + sf.write(os.path.join(wavs_dir, filename), audio_array, sampling_rate) + metadata.append([f"wavs/{filename}", sample[text_header]]) + except Exception as e: + print(f"Error processing sample {idx}: {e}", file=sys.stderr) + continue + + try: + with open(metadata_path, 'w', newline='', encoding='utf-8') as f: + csv.writer(f, delimiter='|').writerows(metadata) + print(f"Dataset saved to {output_dir}") + except Exception as e: + print(f"Error writing metadata: {e}", file=sys.stderr) + +def run_preprocess(input_dir, output_dir, workers): + """ + Runs the preprocessing script with real-time output. + + Args: + input_dir (str): Input directory for preprocessing. + output_dir (str): Output directory for processed data. + workers (int): Number of parallel processes. + """ + script_path = "./src/f5_tts/train/datasets/prepare_csv_wavs.py" + if not os.path.exists(script_path): + print(f"Preprocessing script not found at {script_path}", file=sys.stderr) + return + + command = [ + "python", script_path, + input_dir, output_dir, + "--workers", str(workers) + ] + try: + process = subprocess.Popen( + command, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True, + bufsize=1, # Line buffered + universal_newlines=True + ) + + # Real-time output for stdout and stderr + while True: + stdout_line = process.stdout.readline() + stderr_line = process.stderr.readline() + + if stdout_line: + print(stdout_line, end='', flush=True) + if stderr_line: + print(stderr_line, end='', flush=True, file=sys.stderr) + + if process.poll() is not None: + break + + # Capture any remaining output + stdout, stderr = process.communicate() + if stdout: + print(stdout, end='', flush=True) + if stderr: + print(stderr, end='', flush=True, file=sys.stderr) + + if process.returncode == 0: + print("\nPreprocessing completed successfully.") + else: + print(f"\nPreprocessing failed with return code {process.returncode}.", file=sys.stderr) + except Exception as e: + print(f"Error during preprocessing: {e}", file=sys.stderr) + +if __name__ == "__main__": + # Set up argument parsing + parser = argparse.ArgumentParser(description="Prepare dataset for training.") + # parser.add_argument("--command", type=str, choices=["save", "preprocess"], required=True, + # help="Command to execute: 'save' or 'preprocess'") + parser.add_argument("--output_dir", type=str, default="./data/vin100h-preprocessed-v2", + help="Output directory for save command") + parser.add_argument("--base_model", type=str, default="htdung167/vin100h-preprocessed-v2", + help="Base model for save command") + parser.add_argument("--audio_header", type=str, default="audio", + help="Audio header for save command") + parser.add_argument("--text_header", type=str, default="preprocessed_sentence_v2", + help="Text header for save command") + parser.add_argument("--prepare_csv_input_dir", type=str, + default="./data/vin100h-preprocessed-v2", + help="Input directory for preprocess command") + parser.add_argument("--prepare_csv_output_dir", type=str, + default="./data/vin100h-preprocessed-v2_pinyin", + help="Output directory for preprocess command") + parser.add_argument("--workers", type=int, default=4, + help="Number of parallel processes for preprocess command") + + args = parser.parse_args() + + # if args.command == "save": + save_dataset_to_local_disk(args.output_dir, args.base_model, args.audio_header, args.text_header) + # elif args.command == "preprocess": + run_preprocess(args.prepare_csv_input_dir, args.prepare_csv_output_dir, args.workers) \ No newline at end of file diff --git a/scripts/setup_third_party.py b/scripts/setup_third_party.py new file mode 100644 index 0000000000000000000000000000000000000000..fa6b2a1e54878d65fd9f6ca1d52ae3b2e0d4c54e --- /dev/null +++ b/scripts/setup_third_party.py @@ -0,0 +1,71 @@ +import os +import shutil +import subprocess +import argparse +import sys + +def clone_repository(repo_url, target_dir, branch="main"): + """Clone a git repository to the specified directory with specific branch.""" + if os.path.exists(target_dir): + print(f"Directory {target_dir} already exists. Skipping clone.") + return + + os.makedirs(os.path.dirname(target_dir), exist_ok=True) + + try: + subprocess.run( + ["git", "clone", "-b", branch, repo_url, target_dir], + check=True, + capture_output=True, + text=True + ) + print(f"Successfully cloned {repo_url} (branch: {branch}) to {target_dir}") + except subprocess.CalledProcessError as e: + print(f"Failed to clone repository: {e.stderr}") + sys.exit(1) + +def main(args): + # Define target directories + temp_f5_tts_target_dir = os.path.join("src", "danhtran2mind_f5_tts") + bigvgan_target_dir = os.path.join("src", "third_party", "BigVGAN") + f5_tts_target_dir = os.path.join("src", "f5_tts") + + # Clone F5-TTS repository + clone_repository(args.f5_tts_url, temp_f5_tts_target_dir, args.f5_tts_branch) + + # Clone BigVGAN repository + clone_repository(args.bigvgan_url, bigvgan_target_dir, args.bigvgan_branch) + + # Move the directory + shutil.move(os.path.join(temp_f5_tts_target_dir, "src", "f5_tts"), f5_tts_target_dir) + shutil.move(os.path.join(temp_f5_tts_target_dir, "data"), ".") + # Remove the parent directory + shutil.rmtree(temp_f5_tts_target_dir) + + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description="Clone F5-TTS and BigVGAN repositories") + parser.add_argument( + "--f5-tts-url", + default="https://github.com/danhtran2mind/F5-TTS", + help="URL for F5-TTS repository" + ) + parser.add_argument( + "--bigvgan-url", + default="https://github.com/NVIDIA/BigVGAN", + help="URL for BigVGAN repository" + ) + parser.add_argument( + "--f5-tts-branch", + default="main", + help="Branch for F5-TTS repository" + ) + parser.add_argument( + "--bigvgan-branch", + # default="7d2b454564a6c7d014227f635b7423881f14bdac", + default="main", + help="Branch or commit for BigVGAN repository" + ) + + args = parser.parse_args() + main(args) \ No newline at end of file diff --git a/src/.gitkeep b/src/.gitkeep new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/src/f5_tts/.github/ISSUE_TEMPLATE/bug_report.yml b/src/f5_tts/.github/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 0000000000000000000000000000000000000000..bc88f20b536539c2f14aa761a226d0ea6602464a --- /dev/null +++ b/src/f5_tts/.github/ISSUE_TEMPLATE/bug_report.yml @@ -0,0 +1,50 @@ +name: "Bug Report" +description: | + Please provide as much details to help address the issue more efficiently, including input, output, logs and screenshots. +labels: + - bug +body: + - type: checkboxes + attributes: + label: Checks + description: "To ensure timely help, please confirm the following:" + options: + - label: This template is only for bug reports, usage problems go with 'Help Wanted'. + required: true + - label: I have thoroughly reviewed the project documentation but couldn't find information to solve my problem. + required: true + - label: I have searched for existing issues, including closed ones, and couldn't find a solution. + required: true + - label: I am using English to submit this issue to facilitate community communication. + required: true + - type: textarea + attributes: + label: Environment Details + description: "Provide details including OS, GPU info, Python version, any relevant software or dependencies, and trainer setting." + placeholder: e.g., CentOS Linux 7, 4 * RTX 3090, Python 3.10, torch==2.3.0+cu118, cuda 11.8, config yaml is ... + validations: + required: true + - type: textarea + attributes: + label: Steps to Reproduce + description: | + Include detailed steps, screenshots, and logs. Use the correct markdown syntax for code blocks. + placeholder: | + 1. Create a new conda environment. + 2. Clone the repository, install as local editable and properly set up. + 3. Run the command: `accelerate launch src/f5_tts/train/train.py`. + 4. Have following error message... (attach logs). + validations: + required: true + - type: textarea + attributes: + label: ✔️ Expected Behavior + placeholder: Describe in detail what you expected to happen. + validations: + required: false + - type: textarea + attributes: + label: ❌ Actual Behavior + placeholder: Describe in detail what actually happened. + validations: + required: false \ No newline at end of file diff --git a/src/f5_tts/.github/ISSUE_TEMPLATE/config.yml b/src/f5_tts/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000000000000000000000000000000000000..ac2e8b47ddd5fba554da4139512c9a7e61592578 --- /dev/null +++ b/src/f5_tts/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1 @@ +blank_issues_enabled: false diff --git a/src/f5_tts/.github/ISSUE_TEMPLATE/feature_request.yml b/src/f5_tts/.github/ISSUE_TEMPLATE/feature_request.yml new file mode 100644 index 0000000000000000000000000000000000000000..e54236655a0ae295e870130bffcaff295e307c0a --- /dev/null +++ b/src/f5_tts/.github/ISSUE_TEMPLATE/feature_request.yml @@ -0,0 +1,62 @@ +name: "Feature Request" +description: | + Some constructive suggestions and new ideas regarding current repo. +labels: + - enhancement +body: + - type: checkboxes + attributes: + label: Checks + description: "To help us grasp quickly, please confirm the following:" + options: + - label: This template is only for feature request. + required: true + - label: I have thoroughly reviewed the project documentation but couldn't find any relevant information that meets my needs. + required: true + - label: I have searched for existing issues, including closed ones, and found not discussion yet. + required: true + - label: I am using English to submit this issue to facilitate community communication. + required: true + - type: textarea + attributes: + label: 1. Is this request related to a challenge you're experiencing? Tell us your story. + description: | + Describe the specific problem or scenario you're facing in detail. For example: + *"I was trying to use [feature] for [specific task], but encountered [issue]. This was frustrating because...."* + placeholder: Please describe the situation in as much detail as possible. + validations: + required: true + + - type: textarea + attributes: + label: 2. What is your suggested solution? + description: | + Provide a clear description of the feature or enhancement you'd like to propose. + How would this feature solve your issue or improve the project? + placeholder: Describe your idea or proposed solution here. + validations: + required: true + + - type: textarea + attributes: + label: 3. Additional context or comments + description: | + Any other relevant information, links, documents, or screenshots that provide clarity. + Use this section for anything not covered above. + placeholder: Add any extra details here. + validations: + required: false + + - type: checkboxes + attributes: + label: 4. Can you help us with this feature? + description: | + Let us know if you're interested in contributing. This is not a commitment but a way to express interest in collaboration. + options: + - label: I am interested in contributing to this feature. + required: false + + - type: markdown + attributes: + value: | + **Note:** Please submit only one request per issue to keep discussions focused and manageable. \ No newline at end of file diff --git a/src/f5_tts/.github/ISSUE_TEMPLATE/help_wanted.yml b/src/f5_tts/.github/ISSUE_TEMPLATE/help_wanted.yml new file mode 100644 index 0000000000000000000000000000000000000000..37d6235fa3f2afdc3cbe4722b60e3c4d566ac040 --- /dev/null +++ b/src/f5_tts/.github/ISSUE_TEMPLATE/help_wanted.yml @@ -0,0 +1,54 @@ +name: "Help Wanted" +description: | + Please provide as much details to help address the issue more efficiently, including input, output, logs and screenshots. +labels: + - help wanted +body: + - type: checkboxes + attributes: + label: Checks + description: "To ensure timely help, please confirm the following:" + options: + - label: This template is only for usage issues encountered. + required: true + - label: I have thoroughly reviewed the project documentation but couldn't find information to solve my problem. + required: true + - label: I have searched for existing issues, including closed ones, and couldn't find a solution. + required: true + - label: I am using English to submit this issue to facilitate community communication. + required: true + - type: textarea + attributes: + label: Environment Details + description: "Provide details such as OS, Python version, and any relevant software or dependencies." + placeholder: | + e.g., macOS 13.5, Python 3.10, torch==2.3.0, Gradio 4.44.1 + If training or finetuning related, provide detailed configuration including GPU info and training setup. + validations: + required: true + - type: textarea + attributes: + label: Steps to Reproduce + description: | + Include detailed steps, screenshots, and logs. Provide used prompt wav and text. Use the correct markdown syntax for code blocks. + placeholder: | + 1. Create a new conda environment. + 2. Clone the repository and install as pip package. + 3. Run the command: `f5-tts_infer-gradio` with no ref_text provided. + 4. Stuck there with the following message... (attach logs and also error msg e.g. after ctrl-c). + 5. Prompt & generated wavs are [change suffix to .mp4 to enable direct upload or pack all to .zip]. + 6. Reference audio's transcription or provided ref_text is `xxx`, and text to generate is `xxx`. + validations: + required: true + - type: textarea + attributes: + label: ✔️ Expected Behavior + placeholder: Describe what you expected to happen in detail, e.g. output a generated audio. + validations: + required: false + - type: textarea + attributes: + label: ❌ Actual Behavior + placeholder: Describe what actually happened in detail, failure messages, etc. + validations: + required: false \ No newline at end of file diff --git a/src/f5_tts/.github/ISSUE_TEMPLATE/question.yml b/src/f5_tts/.github/ISSUE_TEMPLATE/question.yml new file mode 100644 index 0000000000000000000000000000000000000000..cbc7ca75c835e9b661d630a6e3986146ad819817 --- /dev/null +++ b/src/f5_tts/.github/ISSUE_TEMPLATE/question.yml @@ -0,0 +1,26 @@ +name: "Question" +description: | + Research question or pure inquiry about the project, usage issue goes with "help wanted". +labels: + - question +body: + - type: checkboxes + attributes: + label: Checks + description: "To help us grasp quickly, please confirm the following:" + options: + - label: This template is only for research question, not usage problems, feature requests or bug reports. + required: true + - label: I have thoroughly reviewed the project documentation and read the related paper(s). + required: true + - label: I have searched for existing issues, including closed ones, no similar questions. + required: true + - label: I am using English to submit this issue to facilitate community communication. + required: true + - type: textarea + attributes: + label: Question details + description: | + Question details, clearly stated using proper markdown syntax. + validations: + required: true diff --git a/src/f5_tts/.github/workflows/pre-commit.yaml b/src/f5_tts/.github/workflows/pre-commit.yaml new file mode 100644 index 0000000000000000000000000000000000000000..2291902267f6961e252f7e91933deaf1a928e412 --- /dev/null +++ b/src/f5_tts/.github/workflows/pre-commit.yaml @@ -0,0 +1,14 @@ +name: pre-commit + +on: + pull_request: + push: + branches: [main] + +jobs: + pre-commit: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-python@v3 + - uses: pre-commit/action@v3.0.1 diff --git a/src/f5_tts/.github/workflows/publish-docker-image.yaml b/src/f5_tts/.github/workflows/publish-docker-image.yaml new file mode 100644 index 0000000000000000000000000000000000000000..502c149e90765f57621dd032f801e9aaf311ed9f --- /dev/null +++ b/src/f5_tts/.github/workflows/publish-docker-image.yaml @@ -0,0 +1,60 @@ +name: Create and publish a Docker image + +# Configures this workflow to run every time a change is pushed to the branch called `release`. +on: + push: + branches: ['main'] + +# Defines two custom environment variables for the workflow. These are used for the Container registry domain, and a name for the Docker image that this workflow builds. +env: + REGISTRY: ghcr.io + IMAGE_NAME: ${{ github.repository }} + +# There is a single job in this workflow. It's configured to run on the latest available version of Ubuntu. +jobs: + build-and-push-image: + runs-on: ubuntu-latest + # Sets the permissions granted to the `GITHUB_TOKEN` for the actions in this job. + permissions: + contents: read + packages: write + # + steps: + - name: Checkout repository + uses: actions/checkout@v4 + - name: Free Up GitHub Actions Ubuntu Runner Disk Space 🔧 + uses: jlumbroso/free-disk-space@main + with: + # This might remove tools that are actually needed, if set to "true" but frees about 6 GB + tool-cache: false + + # All of these default to true, but feel free to set to "false" if necessary for your workflow + android: true + dotnet: true + haskell: true + large-packages: false + swap-storage: false + docker-images: false + # Uses the `docker/login-action` action to log in to the Container registry registry using the account and password that will publish the packages. Once published, the packages are scoped to the account defined here. + - name: Log in to the Container registry + uses: docker/login-action@65b78e6e13532edd9afa3aa52ac7964289d1a9c1 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + # This step uses [docker/metadata-action](https://github.com/docker/metadata-action#about) to extract tags and labels that will be applied to the specified image. The `id` "meta" allows the output of this step to be referenced in a subsequent step. The `images` value provides the base name for the tags and labels. + - name: Extract metadata (tags, labels) for Docker + id: meta + uses: docker/metadata-action@9ec57ed1fcdbf14dcef7dfbe97b2010124a938b7 + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + # This step uses the `docker/build-push-action` action to build the image, based on your repository's `Dockerfile`. If the build succeeds, it pushes the image to GitHub Packages. + # It uses the `context` parameter to define the build's context as the set of files located in the specified path. For more information, see "[Usage](https://github.com/docker/build-push-action#usage)" in the README of the `docker/build-push-action` repository. + # It uses the `tags` and `labels` parameters to tag and label the image with the output from the "meta" step. + - name: Build and push Docker image + uses: docker/build-push-action@f2a1d5e99d037542a71f64918e516c093c6f3fc4 + with: + context: . + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} diff --git a/src/f5_tts/.github/workflows/publish-pypi.yaml b/src/f5_tts/.github/workflows/publish-pypi.yaml new file mode 100644 index 0000000000000000000000000000000000000000..9432daada6a924e436e561331dab69dd94c14f1a --- /dev/null +++ b/src/f5_tts/.github/workflows/publish-pypi.yaml @@ -0,0 +1,66 @@ +# This workflow uses actions that are not certified by GitHub. +# They are provided by a third-party and are governed by +# separate terms of service, privacy policy, and support +# documentation. + +# GitHub recommends pinning actions to a commit SHA. +# To get a newer version, you will need to update the SHA. +# You can also reference a tag or branch, but the action may change without warning. + +name: Upload Python Package + +on: + release: + types: [published] + +permissions: + contents: read + +jobs: + release-build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-python@v5 + with: + python-version: "3.x" + + - name: Build release distributions + run: | + # NOTE: put your own distribution build steps here. + python -m pip install build + python -m build + + - name: Upload distributions + uses: actions/upload-artifact@v4 + with: + name: release-dists + path: dist/ + + pypi-publish: + runs-on: ubuntu-latest + + needs: + - release-build + + permissions: + # IMPORTANT: this permission is mandatory for trusted publishing + id-token: write + + # Dedicated environments with protections for publishing are strongly recommended. + environment: + name: pypi + # OPTIONAL: uncomment and update to include your PyPI project URL in the deployment status: + # url: https://pypi.org/p/YOURPROJECT + + steps: + - name: Retrieve release distributions + uses: actions/download-artifact@v4 + with: + name: release-dists + path: dist/ + + - name: Publish release distributions to PyPI + uses: pypa/gh-action-pypi-publish@release/v1 diff --git a/src/f5_tts/.github/workflows/sync-hf.yaml b/src/f5_tts/.github/workflows/sync-hf.yaml new file mode 100644 index 0000000000000000000000000000000000000000..e2be9e0f772974f577adbb566067d3a3769d37ed --- /dev/null +++ b/src/f5_tts/.github/workflows/sync-hf.yaml @@ -0,0 +1,17 @@ +name: Sync to HF Space + +on: + release: + types: [published] + +jobs: + trigger_curl: + runs-on: ubuntu-latest + + steps: + - name: Send cURL POST request + run: | + curl -X POST https://mrfakename-sync-f5.hf.space/gradio_api/call/refresh \ + -s \ + -H "Content-Type: application/json" \ + -d "{\"data\": [\"${{ secrets.REFRESH_PASSWORD }}\"]}" diff --git a/src/f5_tts/.gitignore b/src/f5_tts/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..993766e9e35bd9d1d22733cb0e742e0d27b080eb --- /dev/null +++ b/src/f5_tts/.gitignore @@ -0,0 +1,171 @@ +# Customed +.vscode/ +tests/ +runs/ +data/ +ckpts/ +wandb/ +results/ + +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +#pdm.lock +# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it +# in version control. +# https://pdm.fming.dev/latest/usage/project/#working-with-version-control +.pdm.toml +.pdm-python +.pdm-build/ + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +#.idea/ diff --git a/src/f5_tts/.gitmodules b/src/f5_tts/.gitmodules new file mode 100644 index 0000000000000000000000000000000000000000..f6ed8f4c661131e538c06468cea7837ebccd1b24 --- /dev/null +++ b/src/f5_tts/.gitmodules @@ -0,0 +1,3 @@ +[submodule "src/third_party/BigVGAN"] + path = src/third_party/BigVGAN + url = https://github.com/NVIDIA/BigVGAN.git diff --git a/src/f5_tts/.pre-commit-config.yaml b/src/f5_tts/.pre-commit-config.yaml new file mode 100644 index 0000000000000000000000000000000000000000..7d0b6a8d53adc6cdb225a61e654592f215d68276 --- /dev/null +++ b/src/f5_tts/.pre-commit-config.yaml @@ -0,0 +1,17 @@ +repos: + - repo: https://github.com/astral-sh/ruff-pre-commit + # Ruff version. + rev: v0.11.2 + hooks: + - id: ruff + name: ruff linter + args: [--fix] + - id: ruff-format + name: ruff formatter + - id: ruff + name: ruff sorter + args: [--select, I, --fix] + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v5.0.0 + hooks: + - id: check-yaml diff --git a/src/f5_tts/Dockerfile b/src/f5_tts/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..14c7d96e8f4576a4b8f778ac649dd5a377251ec8 --- /dev/null +++ b/src/f5_tts/Dockerfile @@ -0,0 +1,30 @@ +FROM pytorch/pytorch:2.4.0-cuda12.4-cudnn9-devel + +USER root + +ARG DEBIAN_FRONTEND=noninteractive + +LABEL github_repo="https://github.com/SWivid/F5-TTS" + +RUN set -x \ + && apt-get update \ + && apt-get -y install wget curl man git less openssl libssl-dev unzip unar build-essential aria2 tmux vim \ + && apt-get install -y openssh-server sox libsox-fmt-all libsox-fmt-mp3 libsndfile1-dev ffmpeg \ + && apt-get install -y librdmacm1 libibumad3 librdmacm-dev libibverbs1 libibverbs-dev ibverbs-utils ibverbs-providers \ + && rm -rf /var/lib/apt/lists/* \ + && apt-get clean + +WORKDIR /workspace + +RUN git clone https://github.com/SWivid/F5-TTS.git \ + && cd F5-TTS \ + && git submodule update --init --recursive \ + && pip install -e . --no-cache-dir + +ENV SHELL=/bin/bash + +VOLUME /root/.cache/huggingface/hub/ + +EXPOSE 7860 + +WORKDIR /workspace/F5-TTS diff --git a/src/f5_tts/LICENSE b/src/f5_tts/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..71c49ce5bf7317f72f00f4ee17f03c174fd069ae --- /dev/null +++ b/src/f5_tts/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 Yushen CHEN + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/src/f5_tts/README.md b/src/f5_tts/README.md new file mode 100644 index 0000000000000000000000000000000000000000..05b4e67f2aa3e4bba94df0fd44f45661ed4c55b2 --- /dev/null +++ b/src/f5_tts/README.md @@ -0,0 +1,261 @@ +# F5-TTS: A Fairytaler that Fakes Fluent and Faithful Speech with Flow Matching + +[![python](https://img.shields.io/badge/Python-3.10-brightgreen)](https://github.com/SWivid/F5-TTS) +[![arXiv](https://img.shields.io/badge/arXiv-2410.06885-b31b1b.svg?logo=arXiv)](https://arxiv.org/abs/2410.06885) +[![demo](https://img.shields.io/badge/GitHub-Demo%20page-orange.svg)](https://swivid.github.io/F5-TTS/) +[![hfspace](https://img.shields.io/badge/🤗-Space%20demo-yellow)](https://huggingface.co/spaces/mrfakename/E2-F5-TTS) +[![msspace](https://img.shields.io/badge/🤖-Space%20demo-blue)](https://modelscope.cn/studios/modelscope/E2-F5-TTS) +[![lab](https://img.shields.io/badge/X--LANCE-Lab-grey?labelColor=lightgrey)](https://x-lance.sjtu.edu.cn/) +[![lab](https://img.shields.io/badge/Peng%20Cheng-Lab-grey?labelColor=lightgrey)](https://www.pcl.ac.cn) + + +**F5-TTS**: Diffusion Transformer with ConvNeXt V2, faster trained and inference. + +**E2 TTS**: Flat-UNet Transformer, closest reproduction from [paper](https://arxiv.org/abs/2406.18009). + +**Sway Sampling**: Inference-time flow step sampling strategy, greatly improves performance + +### Thanks to all the contributors ! + +## News +- **2025/03/12**: 🔥 F5-TTS v1 base model with better training and inference performance. [Few demo](https://swivid.github.io/F5-TTS_updates). +- **2024/10/08**: F5-TTS & E2 TTS base models on [🤗 Hugging Face](https://huggingface.co/SWivid/F5-TTS), [🤖 Model Scope](https://www.modelscope.cn/models/SWivid/F5-TTS_Emilia-ZH-EN), [🟣 Wisemodel](https://wisemodel.cn/models/SJTU_X-LANCE/F5-TTS_Emilia-ZH-EN). + +## Installation + +### Create a separate environment if needed + +```bash +# Create a python 3.10 conda env (you could also use virtualenv) +conda create -n f5-tts python=3.10 +conda activate f5-tts +``` + +### Install PyTorch with matched device + +
+NVIDIA GPU + +> ```bash +> # Install pytorch with your CUDA version, e.g. +> pip install torch==2.4.0+cu124 torchaudio==2.4.0+cu124 --extra-index-url https://download.pytorch.org/whl/cu124 +> ``` + +
+ +
+AMD GPU + +> ```bash +> # Install pytorch with your ROCm version (Linux only), e.g. +> pip install torch==2.5.1+rocm6.2 torchaudio==2.5.1+rocm6.2 --extra-index-url https://download.pytorch.org/whl/rocm6.2 +> ``` + +
+ +
+Intel GPU + +> ```bash +> # Install pytorch with your XPU version, e.g. +> # Intel® Deep Learning Essentials or Intel® oneAPI Base Toolkit must be installed +> pip install torch torchaudio --index-url https://download.pytorch.org/whl/test/xpu +> +> # Intel GPU support is also available through IPEX (Intel® Extension for PyTorch) +> # IPEX does not require the Intel® Deep Learning Essentials or Intel® oneAPI Base Toolkit +> # See: https://pytorch-extension.intel.com/installation?request=platform +> ``` + +
+ +
+Apple Silicon + +> ```bash +> # Install the stable pytorch, e.g. +> pip install torch torchaudio +> ``` + +
+ +### Then you can choose one from below: + +> ### 1. As a pip package (if just for inference) +> +> ```bash +> pip install f5-tts +> ``` +> +> ### 2. Local editable (if also do training, finetuning) +> +> ```bash +> git clone https://github.com/SWivid/F5-TTS.git +> cd F5-TTS +> # git submodule update --init --recursive # (optional, if use bigvgan as vocoder) +> pip install -e . +> ``` + +### Docker usage also available +```bash +# Build from Dockerfile +docker build -t f5tts:v1 . + +# Run from GitHub Container Registry +docker container run --rm -it --gpus=all --mount 'type=volume,source=f5-tts,target=/root/.cache/huggingface/hub/' -p 7860:7860 ghcr.io/swivid/f5-tts:main + +# Quickstart if you want to just run the web interface (not CLI) +docker container run --rm -it --gpus=all --mount 'type=volume,source=f5-tts,target=/root/.cache/huggingface/hub/' -p 7860:7860 ghcr.io/swivid/f5-tts:main f5-tts_infer-gradio --host 0.0.0.0 +``` + +### Runtime + +Deployment solution with Triton and TensorRT-LLM. + +#### Benchmark Results +Decoding on a single L20 GPU, using 26 different prompt_audio & target_text pairs, 16 NFE. + +| Model | Concurrency | Avg Latency | RTF | Mode | +|---------------------|----------------|-------------|--------|-----------------| +| F5-TTS Base (Vocos) | 2 | 253 ms | 0.0394 | Client-Server | +| F5-TTS Base (Vocos) | 1 (Batch_size) | - | 0.0402 | Offline TRT-LLM | +| F5-TTS Base (Vocos) | 1 (Batch_size) | - | 0.1467 | Offline Pytorch | + +See [detailed instructions](src/f5_tts/runtime/triton_trtllm/README.md) for more information. + + +## Inference + +- In order to achieve desired performance, take a moment to read [detailed guidance](src/f5_tts/infer). +- By properly searching the keywords of problem encountered, [issues](https://github.com/SWivid/F5-TTS/issues?q=is%3Aissue) are very helpful. + +### 1. Gradio App + +Currently supported features: + +- Basic TTS with Chunk Inference +- Multi-Style / Multi-Speaker Generation +- Voice Chat powered by Qwen2.5-3B-Instruct +- [Custom inference with more language support](src/f5_tts/infer/SHARED.md) + +```bash +# Launch a Gradio app (web interface) +f5-tts_infer-gradio + +# Specify the port/host +f5-tts_infer-gradio --port 7860 --host 0.0.0.0 + +# Launch a share link +f5-tts_infer-gradio --share +``` + +
+NVIDIA device docker compose file example + +```yaml +services: + f5-tts: + image: ghcr.io/swivid/f5-tts:main + ports: + - "7860:7860" + environment: + GRADIO_SERVER_PORT: 7860 + entrypoint: ["f5-tts_infer-gradio", "--port", "7860", "--host", "0.0.0.0"] + deploy: + resources: + reservations: + devices: + - driver: nvidia + count: 1 + capabilities: [gpu] + +volumes: + f5-tts: + driver: local +``` + +
+ +### 2. CLI Inference + +```bash +# Run with flags +# Leave --ref_text "" will have ASR model transcribe (extra GPU memory usage) +f5-tts_infer-cli --model F5TTS_v1_Base \ +--ref_audio "provide_prompt_wav_path_here.wav" \ +--ref_text "The content, subtitle or transcription of reference audio." \ +--gen_text "Some text you want TTS model generate for you." + +# Run with default setting. src/f5_tts/infer/examples/basic/basic.toml +f5-tts_infer-cli +# Or with your own .toml file +f5-tts_infer-cli -c custom.toml + +# Multi voice. See src/f5_tts/infer/README.md +f5-tts_infer-cli -c src/f5_tts/infer/examples/multi/story.toml +``` + + +## Training + +### 1. With Hugging Face Accelerate + +Refer to [training & finetuning guidance](src/f5_tts/train) for best practice. + +### 2. With Gradio App + +```bash +# Quick start with Gradio web interface +f5-tts_finetune-gradio +``` + +Read [training & finetuning guidance](src/f5_tts/train) for more instructions. + + +## [Evaluation](src/f5_tts/eval) + + +## Development + +Use pre-commit to ensure code quality (will run linters and formatters automatically): + +```bash +pip install pre-commit +pre-commit install +``` + +When making a pull request, before each commit, run: + +```bash +pre-commit run --all-files +``` + +Note: Some model components have linting exceptions for E722 to accommodate tensor notation. + + +## Acknowledgements + +- [E2-TTS](https://arxiv.org/abs/2406.18009) brilliant work, simple and effective +- [Emilia](https://arxiv.org/abs/2407.05361), [WenetSpeech4TTS](https://arxiv.org/abs/2406.05763), [LibriTTS](https://arxiv.org/abs/1904.02882), [LJSpeech](https://keithito.com/LJ-Speech-Dataset/) valuable datasets +- [lucidrains](https://github.com/lucidrains) initial CFM structure with also [bfs18](https://github.com/bfs18) for discussion +- [SD3](https://arxiv.org/abs/2403.03206) & [Hugging Face diffusers](https://github.com/huggingface/diffusers) DiT and MMDiT code structure +- [torchdiffeq](https://github.com/rtqichen/torchdiffeq) as ODE solver, [Vocos](https://huggingface.co/charactr/vocos-mel-24khz) and [BigVGAN](https://github.com/NVIDIA/BigVGAN) as vocoder +- [FunASR](https://github.com/modelscope/FunASR), [faster-whisper](https://github.com/SYSTRAN/faster-whisper), [UniSpeech](https://github.com/microsoft/UniSpeech), [SpeechMOS](https://github.com/tarepan/SpeechMOS) for evaluation tools +- [ctc-forced-aligner](https://github.com/MahmoudAshraf97/ctc-forced-aligner) for speech edit test +- [mrfakename](https://x.com/realmrfakename) huggingface space demo ~ +- [f5-tts-mlx](https://github.com/lucasnewman/f5-tts-mlx/tree/main) Implementation with MLX framework by [Lucas Newman](https://github.com/lucasnewman) +- [F5-TTS-ONNX](https://github.com/DakeQQ/F5-TTS-ONNX) ONNX Runtime version by [DakeQQ](https://github.com/DakeQQ) +- [Yuekai Zhang](https://github.com/yuekaizhang) Triton and TensorRT-LLM support ~ + +## Citation +If our work and codebase is useful for you, please cite as: +``` +@article{chen-etal-2024-f5tts, + title={F5-TTS: A Fairytaler that Fakes Fluent and Faithful Speech with Flow Matching}, + author={Yushen Chen and Zhikang Niu and Ziyang Ma and Keqi Deng and Chunhui Wang and Jian Zhao and Kai Yu and Xie Chen}, + journal={arXiv preprint arXiv:2410.06885}, + year={2024}, +} +``` +## License + +Our code is released under MIT License. The pre-trained models are licensed under the CC-BY-NC license due to the training data Emilia, which is an in-the-wild dataset. Sorry for any inconvenience this may cause. diff --git a/src/f5_tts/ckpts/README.md b/src/f5_tts/ckpts/README.md new file mode 100644 index 0000000000000000000000000000000000000000..b49138882ac4f5e502f54705db64ec32f5ed8caf --- /dev/null +++ b/src/f5_tts/ckpts/README.md @@ -0,0 +1,3 @@ +The pretrained model checkpoints can be reached at https://huggingface.co/SWivid/F5-TTS. + +Scripts will automatically pull model checkpoints from Huggingface, by default to `~/.cache/huggingface/hub/`. diff --git a/src/f5_tts/data/Emilia_ZH_EN_pinyin/vocab.txt b/src/f5_tts/data/Emilia_ZH_EN_pinyin/vocab.txt new file mode 100644 index 0000000000000000000000000000000000000000..cd934390e8f4b3ce98eb319ae618c084d01504b5 --- /dev/null +++ b/src/f5_tts/data/Emilia_ZH_EN_pinyin/vocab.txt @@ -0,0 +1,2545 @@ + +! +" +# +$ +% +& +' +( +) +* ++ +, +- +. +/ +0 +1 +2 +3 +4 +5 +6 +7 +8 +9 +: +; += +> +? +@ +A +B +C +D +E +F +G +H +I +J +K +L +M +N +O +P +Q +R +S +T +U +V +W +X +Y +Z +[ +\ +] +_ +a +a1 +ai1 +ai2 +ai3 +ai4 +an1 +an3 +an4 +ang1 +ang2 +ang4 +ao1 +ao2 +ao3 +ao4 +b +ba +ba1 +ba2 +ba3 +ba4 +bai1 +bai2 +bai3 +bai4 +ban1 +ban2 +ban3 +ban4 +bang1 +bang2 +bang3 +bang4 +bao1 +bao2 +bao3 +bao4 +bei +bei1 +bei2 +bei3 +bei4 +ben1 +ben2 +ben3 +ben4 +beng +beng1 +beng2 +beng3 +beng4 +bi1 +bi2 +bi3 +bi4 +bian1 +bian2 +bian3 +bian4 +biao1 +biao2 +biao3 +bie1 +bie2 +bie3 +bie4 +bin1 +bin4 +bing1 +bing2 +bing3 +bing4 +bo +bo1 +bo2 +bo3 +bo4 +bu2 +bu3 +bu4 +c +ca1 +cai1 +cai2 +cai3 +cai4 +can1 +can2 +can3 +can4 +cang1 +cang2 +cao1 +cao2 +cao3 +ce4 +cen1 +cen2 +ceng1 +ceng2 +ceng4 +cha1 +cha2 +cha3 +cha4 +chai1 +chai2 +chan1 +chan2 +chan3 +chan4 +chang1 +chang2 +chang3 +chang4 +chao1 +chao2 +chao3 +che1 +che2 +che3 +che4 +chen1 +chen2 +chen3 +chen4 +cheng1 +cheng2 +cheng3 +cheng4 +chi1 +chi2 +chi3 +chi4 +chong1 +chong2 +chong3 +chong4 +chou1 +chou2 +chou3 +chou4 +chu1 +chu2 +chu3 +chu4 +chua1 +chuai1 +chuai2 +chuai3 +chuai4 +chuan1 +chuan2 +chuan3 +chuan4 +chuang1 +chuang2 +chuang3 +chuang4 +chui1 +chui2 +chun1 +chun2 +chun3 +chuo1 +chuo4 +ci1 +ci2 +ci3 +ci4 +cong1 +cong2 +cou4 +cu1 +cu4 +cuan1 +cuan2 +cuan4 +cui1 +cui3 +cui4 +cun1 +cun2 +cun4 +cuo1 +cuo2 +cuo4 +d +da +da1 +da2 +da3 +da4 +dai1 +dai2 +dai3 +dai4 +dan1 +dan2 +dan3 +dan4 +dang1 +dang2 +dang3 +dang4 +dao1 +dao2 +dao3 +dao4 +de +de1 +de2 +dei3 +den4 +deng1 +deng2 +deng3 +deng4 +di1 +di2 +di3 +di4 +dia3 +dian1 +dian2 +dian3 +dian4 +diao1 +diao3 +diao4 +die1 +die2 +die4 +ding1 +ding2 +ding3 +ding4 +diu1 +dong1 +dong3 +dong4 +dou1 +dou2 +dou3 +dou4 +du1 +du2 +du3 +du4 +duan1 +duan2 +duan3 +duan4 +dui1 +dui4 +dun1 +dun3 +dun4 +duo1 +duo2 +duo3 +duo4 +e +e1 +e2 +e3 +e4 +ei2 +en1 +en4 +er +er2 +er3 +er4 +f +fa1 +fa2 +fa3 +fa4 +fan1 +fan2 +fan3 +fan4 +fang1 +fang2 +fang3 +fang4 +fei1 +fei2 +fei3 +fei4 +fen1 +fen2 +fen3 +fen4 +feng1 +feng2 +feng3 +feng4 +fo2 +fou2 +fou3 +fu1 +fu2 +fu3 +fu4 +g +ga1 +ga2 +ga3 +ga4 +gai1 +gai2 +gai3 +gai4 +gan1 +gan2 +gan3 +gan4 +gang1 +gang2 +gang3 +gang4 +gao1 +gao2 +gao3 +gao4 +ge1 +ge2 +ge3 +ge4 +gei2 +gei3 +gen1 +gen2 +gen3 +gen4 +geng1 +geng3 +geng4 +gong1 +gong3 +gong4 +gou1 +gou2 +gou3 +gou4 +gu +gu1 +gu2 +gu3 +gu4 +gua1 +gua2 +gua3 +gua4 +guai1 +guai2 +guai3 +guai4 +guan1 +guan2 +guan3 +guan4 +guang1 +guang2 +guang3 +guang4 +gui1 +gui2 +gui3 +gui4 +gun3 +gun4 +guo1 +guo2 +guo3 +guo4 +h +ha1 +ha2 +ha3 +hai1 +hai2 +hai3 +hai4 +han1 +han2 +han3 +han4 +hang1 +hang2 +hang4 +hao1 +hao2 +hao3 +hao4 +he1 +he2 +he4 +hei1 +hen2 +hen3 +hen4 +heng1 +heng2 +heng4 +hong1 +hong2 +hong3 +hong4 +hou1 +hou2 +hou3 +hou4 +hu1 +hu2 +hu3 +hu4 +hua1 +hua2 +hua4 +huai2 +huai4 +huan1 +huan2 +huan3 +huan4 +huang1 +huang2 +huang3 +huang4 +hui1 +hui2 +hui3 +hui4 +hun1 +hun2 +hun4 +huo +huo1 +huo2 +huo3 +huo4 +i +j +ji1 +ji2 +ji3 +ji4 +jia +jia1 +jia2 +jia3 +jia4 +jian1 +jian2 +jian3 +jian4 +jiang1 +jiang2 +jiang3 +jiang4 +jiao1 +jiao2 +jiao3 +jiao4 +jie1 +jie2 +jie3 +jie4 +jin1 +jin2 +jin3 +jin4 +jing1 +jing2 +jing3 +jing4 +jiong3 +jiu1 +jiu2 +jiu3 +jiu4 +ju1 +ju2 +ju3 +ju4 +juan1 +juan2 +juan3 +juan4 +jue1 +jue2 +jue4 +jun1 +jun4 +k +ka1 +ka2 +ka3 +kai1 +kai2 +kai3 +kai4 +kan1 +kan2 +kan3 +kan4 +kang1 +kang2 +kang4 +kao1 +kao2 +kao3 +kao4 +ke1 +ke2 +ke3 +ke4 +ken3 +keng1 +kong1 +kong3 +kong4 +kou1 +kou2 +kou3 +kou4 +ku1 +ku2 +ku3 +ku4 +kua1 +kua3 +kua4 +kuai3 +kuai4 +kuan1 +kuan2 +kuan3 +kuang1 +kuang2 +kuang4 +kui1 +kui2 +kui3 +kui4 +kun1 +kun3 +kun4 +kuo4 +l +la +la1 +la2 +la3 +la4 +lai2 +lai4 +lan2 +lan3 +lan4 +lang1 +lang2 +lang3 +lang4 +lao1 +lao2 +lao3 +lao4 +le +le1 +le4 +lei +lei1 +lei2 +lei3 +lei4 +leng1 +leng2 +leng3 +leng4 +li +li1 +li2 +li3 +li4 +lia3 +lian2 +lian3 +lian4 +liang2 +liang3 +liang4 +liao1 +liao2 +liao3 +liao4 +lie1 +lie2 +lie3 +lie4 +lin1 +lin2 +lin3 +lin4 +ling2 +ling3 +ling4 +liu1 +liu2 +liu3 +liu4 +long1 +long2 +long3 +long4 +lou1 +lou2 +lou3 +lou4 +lu1 +lu2 +lu3 +lu4 +luan2 +luan3 +luan4 +lun1 +lun2 +lun4 +luo1 +luo2 +luo3 +luo4 +lv2 +lv3 +lv4 +lve3 +lve4 +m +ma +ma1 +ma2 +ma3 +ma4 +mai2 +mai3 +mai4 +man1 +man2 +man3 +man4 +mang2 +mang3 +mao1 +mao2 +mao3 +mao4 +me +mei2 +mei3 +mei4 +men +men1 +men2 +men4 +meng +meng1 +meng2 +meng3 +meng4 +mi1 +mi2 +mi3 +mi4 +mian2 +mian3 +mian4 +miao1 +miao2 +miao3 +miao4 +mie1 +mie4 +min2 +min3 +ming2 +ming3 +ming4 +miu4 +mo1 +mo2 +mo3 +mo4 +mou1 +mou2 +mou3 +mu2 +mu3 +mu4 +n +n2 +na1 +na2 +na3 +na4 +nai2 +nai3 +nai4 +nan1 +nan2 +nan3 +nan4 +nang1 +nang2 +nang3 +nao1 +nao2 +nao3 +nao4 +ne +ne2 +ne4 +nei3 +nei4 +nen4 +neng2 +ni1 +ni2 +ni3 +ni4 +nian1 +nian2 +nian3 +nian4 +niang2 +niang4 +niao2 +niao3 +niao4 +nie1 +nie4 +nin2 +ning2 +ning3 +ning4 +niu1 +niu2 +niu3 +niu4 +nong2 +nong4 +nou4 +nu2 +nu3 +nu4 +nuan3 +nuo2 +nuo4 +nv2 +nv3 +nve4 +o +o1 +o2 +ou1 +ou2 +ou3 +ou4 +p +pa1 +pa2 +pa4 +pai1 +pai2 +pai3 +pai4 +pan1 +pan2 +pan4 +pang1 +pang2 +pang4 +pao1 +pao2 +pao3 +pao4 +pei1 +pei2 +pei4 +pen1 +pen2 +pen4 +peng1 +peng2 +peng3 +peng4 +pi1 +pi2 +pi3 +pi4 +pian1 +pian2 +pian4 +piao1 +piao2 +piao3 +piao4 +pie1 +pie2 +pie3 +pin1 +pin2 +pin3 +pin4 +ping1 +ping2 +po1 +po2 +po3 +po4 +pou1 +pu1 +pu2 +pu3 +pu4 +q +qi1 +qi2 +qi3 +qi4 +qia1 +qia3 +qia4 +qian1 +qian2 +qian3 +qian4 +qiang1 +qiang2 +qiang3 +qiang4 +qiao1 +qiao2 +qiao3 +qiao4 +qie1 +qie2 +qie3 +qie4 +qin1 +qin2 +qin3 +qin4 +qing1 +qing2 +qing3 +qing4 +qiong1 +qiong2 +qiu1 +qiu2 +qiu3 +qu1 +qu2 +qu3 +qu4 +quan1 +quan2 +quan3 +quan4 +que1 +que2 +que4 +qun2 +r +ran2 +ran3 +rang1 +rang2 +rang3 +rang4 +rao2 +rao3 +rao4 +re2 +re3 +re4 +ren2 +ren3 +ren4 +reng1 +reng2 +ri4 +rong1 +rong2 +rong3 +rou2 +rou4 +ru2 +ru3 +ru4 +ruan2 +ruan3 +rui3 +rui4 +run4 +ruo4 +s +sa1 +sa2 +sa3 +sa4 +sai1 +sai4 +san1 +san2 +san3 +san4 +sang1 +sang3 +sang4 +sao1 +sao2 +sao3 +sao4 +se4 +sen1 +seng1 +sha1 +sha2 +sha3 +sha4 +shai1 +shai2 +shai3 +shai4 +shan1 +shan3 +shan4 +shang +shang1 +shang3 +shang4 +shao1 +shao2 +shao3 +shao4 +she1 +she2 +she3 +she4 +shei2 +shen1 +shen2 +shen3 +shen4 +sheng1 +sheng2 +sheng3 +sheng4 +shi +shi1 +shi2 +shi3 +shi4 +shou1 +shou2 +shou3 +shou4 +shu1 +shu2 +shu3 +shu4 +shua1 +shua2 +shua3 +shua4 +shuai1 +shuai3 +shuai4 +shuan1 +shuan4 +shuang1 +shuang3 +shui2 +shui3 +shui4 +shun3 +shun4 +shuo1 +shuo4 +si1 +si2 +si3 +si4 +song1 +song3 +song4 +sou1 +sou3 +sou4 +su1 +su2 +su4 +suan1 +suan4 +sui1 +sui2 +sui3 +sui4 +sun1 +sun3 +suo +suo1 +suo2 +suo3 +t +ta1 +ta2 +ta3 +ta4 +tai1 +tai2 +tai4 +tan1 +tan2 +tan3 +tan4 +tang1 +tang2 +tang3 +tang4 +tao1 +tao2 +tao3 +tao4 +te4 +teng2 +ti1 +ti2 +ti3 +ti4 +tian1 +tian2 +tian3 +tiao1 +tiao2 +tiao3 +tiao4 +tie1 +tie2 +tie3 +tie4 +ting1 +ting2 +ting3 +tong1 +tong2 +tong3 +tong4 +tou +tou1 +tou2 +tou4 +tu1 +tu2 +tu3 +tu4 +tuan1 +tuan2 +tui1 +tui2 +tui3 +tui4 +tun1 +tun2 +tun4 +tuo1 +tuo2 +tuo3 +tuo4 +u +v +w +wa +wa1 +wa2 +wa3 +wa4 +wai1 +wai3 +wai4 +wan1 +wan2 +wan3 +wan4 +wang1 +wang2 +wang3 +wang4 +wei1 +wei2 +wei3 +wei4 +wen1 +wen2 +wen3 +wen4 +weng1 +weng4 +wo1 +wo2 +wo3 +wo4 +wu1 +wu2 +wu3 +wu4 +x +xi1 +xi2 +xi3 +xi4 +xia1 +xia2 +xia4 +xian1 +xian2 +xian3 +xian4 +xiang1 +xiang2 +xiang3 +xiang4 +xiao1 +xiao2 +xiao3 +xiao4 +xie1 +xie2 +xie3 +xie4 +xin1 +xin2 +xin4 +xing1 +xing2 +xing3 +xing4 +xiong1 +xiong2 +xiu1 +xiu3 +xiu4 +xu +xu1 +xu2 +xu3 +xu4 +xuan1 +xuan2 +xuan3 +xuan4 +xue1 +xue2 +xue3 +xue4 +xun1 +xun2 +xun4 +y +ya +ya1 +ya2 +ya3 +ya4 +yan1 +yan2 +yan3 +yan4 +yang1 +yang2 +yang3 +yang4 +yao1 +yao2 +yao3 +yao4 +ye1 +ye2 +ye3 +ye4 +yi +yi1 +yi2 +yi3 +yi4 +yin1 +yin2 +yin3 +yin4 +ying1 +ying2 +ying3 +ying4 +yo1 +yong1 +yong2 +yong3 +yong4 +you1 +you2 +you3 +you4 +yu1 +yu2 +yu3 +yu4 +yuan1 +yuan2 +yuan3 +yuan4 +yue1 +yue4 +yun1 +yun2 +yun3 +yun4 +z +za1 +za2 +za3 +zai1 +zai3 +zai4 +zan1 +zan2 +zan3 +zan4 +zang1 +zang4 +zao1 +zao2 +zao3 +zao4 +ze2 +ze4 +zei2 +zen3 +zeng1 +zeng4 +zha1 +zha2 +zha3 +zha4 +zhai1 +zhai2 +zhai3 +zhai4 +zhan1 +zhan2 +zhan3 +zhan4 +zhang1 +zhang2 +zhang3 +zhang4 +zhao1 +zhao2 +zhao3 +zhao4 +zhe +zhe1 +zhe2 +zhe3 +zhe4 +zhen1 +zhen2 +zhen3 +zhen4 +zheng1 +zheng2 +zheng3 +zheng4 +zhi1 +zhi2 +zhi3 +zhi4 +zhong1 +zhong2 +zhong3 +zhong4 +zhou1 +zhou2 +zhou3 +zhou4 +zhu1 +zhu2 +zhu3 +zhu4 +zhua1 +zhua2 +zhua3 +zhuai1 +zhuai3 +zhuai4 +zhuan1 +zhuan2 +zhuan3 +zhuan4 +zhuang1 +zhuang4 +zhui1 +zhui4 +zhun1 +zhun2 +zhun3 +zhuo1 +zhuo2 +zi +zi1 +zi2 +zi3 +zi4 +zong1 +zong2 +zong3 +zong4 +zou1 +zou2 +zou3 +zou4 +zu1 +zu2 +zu3 +zuan1 +zuan3 +zuan4 +zui2 +zui3 +zui4 +zun1 +zuo +zuo1 +zuo2 +zuo3 +zuo4 +{ +~ +¡ +¢ +£ +¥ +§ +¨ +© +« +® +¯ +° +± +² +³ +´ +µ +· +¹ +º +» +¼ +½ +¾ +¿ +À +Á + +à +Ä +Å +Æ +Ç +È +É +Ê +Í +Î +Ñ +Ó +Ö +× +Ø +Ú +Ü +Ý +Þ +ß +à +á +â +ã +ä +å +æ +ç +è +é +ê +ë +ì +í +î +ï +ð +ñ +ò +ó +ô +õ +ö +ø +ù +ú +û +ü +ý +Ā +ā +ă +ą +ć +Č +č +Đ +đ +ē +ė +ę +ě +ĝ +ğ +ħ +ī +į +İ +ı +Ł +ł +ń +ņ +ň +ŋ +Ō +ō +ő +œ +ř +Ś +ś +Ş +ş +Š +š +Ť +ť +ũ +ū +ź +Ż +ż +Ž +ž +ơ +ư +ǎ +ǐ +ǒ +ǔ +ǚ +ș +ț +ɑ +ɔ +ɕ +ə +ɛ +ɜ +ɡ +ɣ +ɪ +ɫ +ɴ +ɹ +ɾ +ʃ +ʊ +ʌ +ʒ +ʔ +ʰ +ʷ +ʻ +ʾ +ʿ +ˈ +ː +˙ +˜ +ˢ +́ +̅ +Α +Β +Δ +Ε +Θ +Κ +Λ +Μ +Ξ +Π +Σ +Τ +Φ +Χ +Ψ +Ω +ά +έ +ή +ί +α +β +γ +δ +ε +ζ +η +θ +ι +κ +λ +μ +ν +ξ +ο +π +ρ +ς +σ +τ +υ +φ +χ +ψ +ω +ϊ +ό +ύ +ώ +ϕ +ϵ +Ё +А +Б +В +Г +Д +Е +Ж +З +И +Й +К +Л +М +Н +О +П +Р +С +Т +У +Ф +Х +Ц +Ч +Ш +Щ +Ы +Ь +Э +Ю +Я +а +б +в +г +д +е +ж +з +и +й +к +л +м +н +о +п +р +с +т +у +ф +х +ц +ч +ш +щ +ъ +ы +ь +э +ю +я +ё +і +ְ +ִ +ֵ +ֶ +ַ +ָ +ֹ +ּ +־ +ׁ +א +ב +ג +ד +ה +ו +ז +ח +ט +י +כ +ל +ם +מ +ן +נ +ס +ע +פ +ק +ר +ש +ת +أ +ب +ة +ت +ج +ح +د +ر +ز +س +ص +ط +ع +ق +ك +ل +م +ن +ه +و +ي +َ +ُ +ِ +ْ +ก +ข +ง +จ +ต +ท +น +ป +ย +ร +ว +ส +ห +อ +ฮ +ั +า +ี +ึ +โ +ใ +ไ +่ +้ +์ +ḍ +Ḥ +ḥ +ṁ +ṃ +ṅ +ṇ +Ṛ +ṛ +Ṣ +ṣ +Ṭ +ṭ +ạ +ả +Ấ +ấ +ầ +ậ +ắ +ằ +ẻ +ẽ +ế +ề +ể +ễ +ệ +ị +ọ +ỏ +ố +ồ +ộ +ớ +ờ +ở +ụ +ủ +ứ +ữ +ἀ +ἁ +Ἀ +ἐ +ἔ +ἰ +ἱ +ὀ +ὁ +ὐ +ὲ +ὸ +ᾶ +᾽ +ῆ +ῇ +ῶ +‎ +‑ +‒ +– +— +― +‖ +† +‡ +• +… +‧ +‬ +′ +″ +⁄ +⁡ +⁰ +⁴ +⁵ +⁶ +⁷ +⁸ +⁹ +₁ +₂ +₃ +€ +₱ +₹ +₽ +℃ +ℏ +ℓ +№ +ℝ +™ +⅓ +⅔ +⅛ +→ +∂ +∈ +∑ +− +∗ +√ +∞ +∫ +≈ +≠ +≡ +≤ +≥ +⋅ +⋯ +█ +♪ +⟨ +⟩ +、 +。 +《 +》 +「 +」 +【 +】 +あ +う +え +お +か +が +き +ぎ +く +ぐ +け +げ +こ +ご +さ +し +じ +す +ず +せ +ぜ +そ +ぞ +た +だ +ち +っ +つ +で +と +ど +な +に +ね +の +は +ば +ひ +ぶ +へ +べ +ま +み +む +め +も +ゃ +や +ゆ +ょ +よ +ら +り +る +れ +ろ +わ +を +ん +ァ +ア +ィ +イ +ウ +ェ +エ +オ +カ +ガ +キ +ク +ケ +ゲ +コ +ゴ +サ +ザ +シ +ジ +ス +ズ +セ +ゾ +タ +ダ +チ +ッ +ツ +テ +デ +ト +ド +ナ +ニ +ネ +ノ +バ +パ +ビ +ピ +フ +プ +ヘ +ベ +ペ +ホ +ボ +ポ +マ +ミ +ム +メ +モ +ャ +ヤ +ュ +ユ +ョ +ヨ +ラ +リ +ル +レ +ロ +ワ +ン +・ +ー +ㄋ +ㄍ +ㄎ +ㄏ +ㄓ +ㄕ +ㄚ +ㄜ +ㄟ +ㄤ +ㄥ +ㄧ +ㄱ +ㄴ +ㄷ +ㄹ +ㅁ +ㅂ +ㅅ +ㅈ +ㅍ +ㅎ +ㅏ +ㅓ +ㅗ +ㅜ +ㅡ +ㅣ +㗎 +가 +각 +간 +갈 +감 +갑 +갓 +갔 +강 +같 +개 +거 +건 +걸 +겁 +것 +겉 +게 +겠 +겨 +결 +겼 +경 +계 +고 +곤 +골 +곱 +공 +과 +관 +광 +교 +구 +국 +굴 +귀 +귄 +그 +근 +글 +금 +기 +긴 +길 +까 +깍 +깔 +깜 +깨 +께 +꼬 +꼭 +꽃 +꾸 +꿔 +끔 +끗 +끝 +끼 +나 +난 +날 +남 +납 +내 +냐 +냥 +너 +넘 +넣 +네 +녁 +년 +녕 +노 +녹 +놀 +누 +눈 +느 +는 +늘 +니 +님 +닙 +다 +닥 +단 +달 +닭 +당 +대 +더 +덕 +던 +덥 +데 +도 +독 +동 +돼 +됐 +되 +된 +될 +두 +둑 +둥 +드 +들 +등 +디 +따 +딱 +딸 +땅 +때 +떤 +떨 +떻 +또 +똑 +뚱 +뛰 +뜻 +띠 +라 +락 +란 +람 +랍 +랑 +래 +랜 +러 +런 +럼 +렇 +레 +려 +력 +렵 +렸 +로 +록 +롬 +루 +르 +른 +를 +름 +릉 +리 +릴 +림 +마 +막 +만 +많 +말 +맑 +맙 +맛 +매 +머 +먹 +멍 +메 +면 +명 +몇 +모 +목 +몸 +못 +무 +문 +물 +뭐 +뭘 +미 +민 +밌 +밑 +바 +박 +밖 +반 +받 +발 +밤 +밥 +방 +배 +백 +밸 +뱀 +버 +번 +벌 +벚 +베 +벼 +벽 +별 +병 +보 +복 +본 +볼 +봐 +봤 +부 +분 +불 +비 +빔 +빛 +빠 +빨 +뼈 +뽀 +뿅 +쁘 +사 +산 +살 +삼 +샀 +상 +새 +색 +생 +서 +선 +설 +섭 +섰 +성 +세 +셔 +션 +셨 +소 +속 +손 +송 +수 +숙 +순 +술 +숫 +숭 +숲 +쉬 +쉽 +스 +슨 +습 +슷 +시 +식 +신 +실 +싫 +심 +십 +싶 +싸 +써 +쓰 +쓴 +씌 +씨 +씩 +씬 +아 +악 +안 +않 +알 +야 +약 +얀 +양 +얘 +어 +언 +얼 +엄 +업 +없 +었 +엉 +에 +여 +역 +연 +염 +엽 +영 +옆 +예 +옛 +오 +온 +올 +옷 +옹 +와 +왔 +왜 +요 +욕 +용 +우 +운 +울 +웃 +워 +원 +월 +웠 +위 +윙 +유 +육 +윤 +으 +은 +을 +음 +응 +의 +이 +익 +인 +일 +읽 +임 +입 +있 +자 +작 +잔 +잖 +잘 +잡 +잤 +장 +재 +저 +전 +점 +정 +제 +져 +졌 +조 +족 +좀 +종 +좋 +죠 +주 +준 +줄 +중 +줘 +즈 +즐 +즘 +지 +진 +집 +짜 +짝 +쩌 +쪼 +쪽 +쫌 +쭈 +쯔 +찌 +찍 +차 +착 +찾 +책 +처 +천 +철 +체 +쳐 +쳤 +초 +촌 +추 +출 +춤 +춥 +춰 +치 +친 +칠 +침 +칩 +칼 +커 +켓 +코 +콩 +쿠 +퀴 +크 +큰 +큽 +키 +킨 +타 +태 +터 +턴 +털 +테 +토 +통 +투 +트 +특 +튼 +틀 +티 +팀 +파 +팔 +패 +페 +펜 +펭 +평 +포 +폭 +표 +품 +풍 +프 +플 +피 +필 +하 +학 +한 +할 +함 +합 +항 +해 +햇 +했 +행 +허 +험 +형 +혜 +호 +혼 +홀 +화 +회 +획 +후 +휴 +흐 +흔 +희 +히 +힘 +ﷺ +ﷻ +! +, +? +� +𠮶 diff --git a/src/f5_tts/data/librispeech_pc_test_clean_cross_sentence.lst b/src/f5_tts/data/librispeech_pc_test_clean_cross_sentence.lst new file mode 100644 index 0000000000000000000000000000000000000000..347dbfd284689f2c173305e3fb7008613aaa7661 --- /dev/null +++ b/src/f5_tts/data/librispeech_pc_test_clean_cross_sentence.lst @@ -0,0 +1,1127 @@ +4992-41806-0009 4.355 exclaimed Bill Harmon to his wife as they went through the lighted hall. 4992-23283-0000 6.645 But the more forgetfulness had then prevailed, the more powerful was the force of remembrance when she awoke. +4992-23283-0001 2.71 Miss Milner's health is not good"! 4992-23283-0003 4.645 So there is to me"! added Sandford, with a sarcastic sneer. +4992-23283-0015 3.675 Is she not afraid that I will thwart her inclinations"? 4992-23283-0004 8.06 And yet you must own her behaviour has warranted them has it not been in this particular incoherent and unaccountable"? +4992-23283-0015 3.675 Is she not afraid that I will thwart her inclinations"? 4992-23283-0007 4.045 To ask any more questions of you, I believe, would be unfair. +4992-41797-0012 2.705 She is wild to know how to do things. 4992-23283-0008 4.91 He seemed to wait for her reply; but as she made none, he proceeded- +4992-41797-0016 3.3 They couldn't run nor move; they're just pasteboard". 4992-23283-0009 8.395 Oh! my Lord," cried Miss Woodley, with a most forcible accent, " You are the last person on earth she would pardon me for entrusting". +4992-41797-0005 3.845 Done? He ain't done a thing he'd oughter sence he was born. 4992-23283-0010 5 But in such a case, Miss Milner's election of a husband shall not direct mine. +4992-41797-0012 2.705 She is wild to know how to do things. 4992-23283-0011 4.225 If she does not know how to estimate her own value, I do. +4992-41806-0004 3.7 Burn, fire, burn! Flicker, flicker, flame! 4992-23283-0013 6.63 My Lord, Miss Milner's taste is not a depraved one; it is but too refined". +4992-41797-0012 2.705 She is wild to know how to do things. 4992-23283-0014 4.535 What can you mean by that, Miss Woodley? You talk mysteriously. +4992-41797-0012 2.705 She is wild to know how to do things. 4992-23283-0016 4.495 Again he searched his own thoughts; nor ineffectually as before. +4992-23283-0007 4.045 To ask any more questions of you, I believe, would be unfair. 4992-23283-0018 6.575 To relieve her from both, he laid his hand with force upon his heart, and said, "Do you believe me"? +4992-23283-0016 4.495 Again he searched his own thoughts; nor ineffectually as before. 4992-23283-0019 6.585 I will make no unjust use of what I know," he replied with firmness. "I believe you, my Lord". +672-122797-0005 3.26 Oh, that made him so angry! 672-122797-0000 4.07 Out in the woods stood a nice little Fir Tree. +672-122797-0029 3.05 How it will shine this evening"! 672-122797-0003 4.76 But this was what the Tree could not bear to hear. +672-122797-0000 4.07 Out in the woods stood a nice little Fir Tree. 672-122797-0007 6.42 In autumn the wood cutters always came and felled some of the largest trees. +672-122797-0000 4.07 Out in the woods stood a nice little Fir Tree. 672-122797-0012 7.765 I would fain know if I am destined for so glorious a career," cried the Tree, rejoicing. +672-122797-0029 3.05 How it will shine this evening"! 672-122797-0013 8.705 I am now tall, and my branches spread like the others that were carried off last year! Oh! +672-122797-0032 4 cried the young ladies, and they quickly put out the fire. 672-122797-0015 4.455 Were I in the warm room with all the splendor and magnificence! +672-122797-0044 3.74 And he leaned against the wall lost in reverie. 672-122797-0016 9.215 Yes; then something better, something still grander, will surely follow, or wherefore should they thus ornament me? +672-122797-0041 3.88 In the morning the servant and the housemaid came in. 672-122797-0017 4.82 Something better, something still grander must follow - but what? +672-122797-0000 4.07 Out in the woods stood a nice little Fir Tree. 672-122797-0018 4.93 Rejoice in our presence"! said the Air and the Sunlight. +672-122797-0047 3.325 How kind man is, after all! 672-122797-0019 4.11 Rejoice in thy own fresh youth"! +672-122797-0053 2.955 They were so extremely curious. 672-122797-0020 8.825 But the Tree did not rejoice at all; he grew and grew, and was green both winter and summer. +672-122797-0032 4 cried the young ladies, and they quickly put out the fire. 672-122797-0021 4.15 and towards Christmas he was one of the first that was cut down. +672-122797-0032 4 cried the young ladies, and they quickly put out the fire. 672-122797-0023 9.695063 He well knew that he should never see his dear old comrades, the little bushes and flowers around him, anymore; perhaps not even the birds! +672-122797-0059 3.52 Only that one," answered the Tree. 672-122797-0024 4.13 The departure was not at all agreeable. +672-122797-0000 4.07 Out in the woods stood a nice little Fir Tree. 672-122797-0027 4.79 The servants, as well as the young ladies, decorated it. +672-122797-0015 4.455 Were I in the warm room with all the splendor and magnificence! 672-122797-0030 4.575 Perhaps the other trees from the forest will come to look at me! +672-122797-0015 4.455 Were I in the warm room with all the splendor and magnificence! 672-122797-0032 4 cried the young ladies, and they quickly put out the fire. +672-122797-0015 4.455 Were I in the warm room with all the splendor and magnificence! 672-122797-0034 5.11 A story"! cried the children, drawing a little fat man towards the Tree. +672-122797-0011 2.54 And then? What happens then"? 672-122797-0036 5.365 Humpy Dumpy fell downstairs, and yet he married the princess! +672-122797-0044 3.74 And he leaned against the wall lost in reverie. 672-122797-0038 8.8 thought the Fir Tree, and believed it all, because the man who told the story was so good looking. "Well, well! +672-122797-0043 3.78 What's the meaning of this"? thought the Tree. 672-122797-0039 4.025 I won't tremble tomorrow"! thought the Fir Tree. +672-122797-0000 4.07 Out in the woods stood a nice little Fir Tree. 672-122797-0040 5.125 And the whole night the Tree stood still and in deep thought. +672-122797-0059 3.52 Only that one," answered the Tree. 672-122797-0046 4.715 Tis now winter out of doors"! thought the Tree. +672-122797-0054 4.25 I know no such place," said the Tree. 672-122797-0048 6.555 If it only were not so dark here, and so terribly lonely! +672-122797-0041 3.88 In the morning the servant and the housemaid came in. 672-122797-0050 4.855 They snuffed about the Fir Tree, and rustled among the branches. +672-122797-0054 4.25 I know no such place," said the Tree. 672-122797-0051 4.665 I am by no means old," said the Fir Tree. +672-122797-0011 2.54 And then? What happens then"? 672-122797-0052 4.285 There's many a one considerably older than I am". +672-122797-0031 3.98 It blazed up famously. "Help! Help"! 672-122797-0054 4.25 I know no such place," said the Tree. +672-122797-0032 4 cried the young ladies, and they quickly put out the fire. 672-122797-0055 8.23 And then he told all about his youth; and the little Mice had never heard the like before; and they listened and said, +672-122797-0000 4.07 Out in the woods stood a nice little Fir Tree. 672-122797-0056 5.225 said the Fir Tree, thinking over what he had himself related. +672-122797-0065 3.03 Now that too is over. 672-122797-0057 6.56 Yes, in reality those were happy times". +672-122797-0000 4.07 Out in the woods stood a nice little Fir Tree. 672-122797-0058 4.47 Who is Humpy Dumpy"? asked the Mice. +672-122797-0005 3.26 Oh, that made him so angry! 672-122797-0061 7.59 Don't you know one about bacon and tallow candles? Can't you tell any larder stories"? +672-122797-0021 4.15 and towards Christmas he was one of the first that was cut down. 672-122797-0066 4.815 Why, one morning there came a quantity of people and set to work in the loft. +672-122797-0010 3.815 Rejoice in thy growth"! said the Sunbeams. 672-122797-0068 4.02 but it was not the Fir Tree that they meant. +672-122797-0028 2.61 This evening"! they all said. 672-122797-0069 5.01 It was in a corner that he lay, among weeds and nettles. +672-122797-0032 4 cried the young ladies, and they quickly put out the fire. 672-122797-0070 6.27 The golden star of tinsel was still on the top of the Tree, and glittered in the sunshine. +672-122797-0021 4.15 and towards Christmas he was one of the first that was cut down. 672-122797-0071 8.875 In the court yard some of the merry children were playing who had danced at Christmas round the Fir Tree, and were so glad at the sight of him. +672-122797-0000 4.07 Out in the woods stood a nice little Fir Tree. 672-122797-0072 7.94 And the gardener's boy chopped the Tree into small pieces; there was a whole heap lying there. +672-122797-0053 2.955 They were so extremely curious. 672-122797-0073 8.205 The wood flamed up splendidly under the large brewing copper, and it sighed so deeply! +672-122797-0062 2.675 No," said the Tree. 672-122797-0074 8.73 However, that was over now - the Tree gone, the story at an end. +2961-961-0005 3.775 Some poems of Solon were recited by the boys. 2961-960-0001 8.250063 The influence with the Timaeus has exercised upon posterity is due partly to a misunderstanding. +2961-961-0005 3.775 Some poems of Solon were recited by the boys. 2961-960-0004 8.22 There is no danger of the modern commentators on the Timaeus falling into the absurdities of the Neo Platonists. +2961-961-0005 3.775 Some poems of Solon were recited by the boys. 2961-960-0007 7.64 But they have nothing to do with the interpretation of Plato, and in spirit they are opposed to him. +2961-961-0005 3.775 Some poems of Solon were recited by the boys. 2961-960-0012 6.89 Many, if not all the elements of the Pre Socratic philosophy are included in the Timaeus. +2961-961-0005 3.775 Some poems of Solon were recited by the boys. 2961-960-0014 8.775 The ideas also remain, but they have become types in nature, forms of men, animals, birds, fishes. +2961-961-0005 3.775 Some poems of Solon were recited by the boys. 2961-960-0015 7.83 The style and plan of the Timaeus differ greatly from that of any other of the Platonic dialogues. +2961-961-0005 3.775 Some poems of Solon were recited by the boys. 2961-960-0016 7.76 But Plato has not the same mastery over his instrument which he exhibits in the Phaedrus or Symposium. +2961-961-0005 3.775 Some poems of Solon were recited by the boys. 2961-960-0017 7.87 Nothing can exceed the beauty or art of the introduction, in which he is using words after his accustomed manner. +2961-961-0005 3.775 Some poems of Solon were recited by the boys. 2961-960-0018 8.38 But in the rest of the work the power of language seems to fail him, and the dramatic form is wholly given up. +2961-961-0005 3.775 Some poems of Solon were recited by the boys. 2961-960-0020 9.88 And hence we find the same sort of clumsiness in the Timaeus of Plato which characterizes the philosophical poem of Lucretius. +2961-961-0005 3.775 Some poems of Solon were recited by the boys. 2961-960-0022 7.425 Plato had not the command of his materials which would have enabled him to produce a perfect work of art. +8224-274384-0003 3.87 or hath he given us any gift? 8224-274381-0011 6.48 His conduct and presence of mind in this emergence appeared conspicuous. +1221-135766-0013 3.645 Pearl was a born outcast of the infantile world. 1221-135767-0005 5.865 It was the scarlet letter in another form: the scarlet letter endowed with life! +1221-135766-0015 2.63 If spoken to, she would not speak again. 1221-135767-0010 8.2 She screamed and shouted, too, with a terrific volume of sound, which, doubtless, caused the hearts of the fugitives to quake within them. +1221-135767-0008 3.095 Come, therefore, and let us fling mud at them"! 1221-135767-0014 7.07 Yea, his honourable worship is within. But he hath a godly minister or two with him, and likewise a leech. +1221-135767-0020 3.345 In truth, she seemed absolutely hidden behind it. 1221-135767-0024 5.85 Pearl, seeing the rose bushes, began to cry for a red rose, and would not be pacified. +7176-88083-0008 3.28 In despair he hurled himself downward too soon. 7176-92135-0001 7.56 In short he becomes a "prominent figure in London Society" - and, if he is not careful, somebody will say so. +7176-92135-0007 3.275 Anyhow it's jolly exciting, and I can do the dialogue all right. 7176-92135-0005 5.47 But suppose you said, "I'm fond of writing; my people always say my letters home are good enough for Punch. +7176-92135-0027 2.835 Lady Larkspur starts suddenly and turns towards him. 7176-92135-0006 7.795 I've got a little idea for a play about a man and a woman and another woman, and - but perhaps I'd better keep the plot a secret for the moment. +7176-88083-0009 4.045 The great hawk followed hurriedly, to retrieve his prey from the ground. 7176-92135-0008 4.43 Lend me your ear for ten minutes, and you shall learn just what stagecraft is". +7176-92135-0004 2.425 Frankly I cannot always say. 7176-92135-0009 4.38 And I should begin with a short homily on Soliloquy. +7176-88083-0016 3.92 Straightway the hawk glided from his perch and darted after him. 7176-92135-0015 6.755 And so on, till you get to the end, when Ophelia might say, "Ah, yes," or something non committal of that sort. +7176-88083-0006 4.295 It might have seemed that a trout of this size was a fairly substantial meal. 7176-92135-0016 7.545 This would be an easy way of doing it, but it would not be the best way, for the reason that it is too easy to call attention to itself. +7176-88083-0016 3.92 Straightway the hawk glided from his perch and darted after him. 7176-92135-0017 7.17 In the old badly made play it was frequently necessary for one of the characters to take the audience into his confidence. +7176-88083-0016 3.92 Straightway the hawk glided from his perch and darted after him. 7176-92135-0018 8.94 In the modern well constructed play he simply rings up an imaginary confederate and tells him what he is going to do. Could anything be more natural? +7176-88083-0008 3.28 In despair he hurled himself downward too soon. 7176-92135-0020 7.165 Double nine two three, Elsinore.... Double- nine, yes.... Hallo, is that you, Horatio? Hamlet speaking. +7176-88083-0016 3.92 Straightway the hawk glided from his perch and darted after him. 7176-92135-0022 8.23 To be or not to be, that is the question; whether 'tis nobler in the mind to suffer the slings and arrows - What? No, Hamlet speaking. +7176-92135-0002 3.415 But even the unsuccessful dramatist has his moments. 7176-92135-0023 6.215 You gave me double- five, I want double- nine.... Hallo, is that you, Horatio? Hamlet speaking. +7176-92135-0026 2.95 Enter Hamlet with his favourite boar hound. 7176-92135-0024 4.1 To be or not to be, that is the question; whether 'tis nobler +7176-88083-0016 3.92 Straightway the hawk glided from his perch and darted after him. 7176-92135-0042 7.27 In novels the hero has often "pushed his meals away untasted," but no stage hero would do anything so unnatural as this. +7176-92135-0007 3.275 Anyhow it's jolly exciting, and I can do the dialogue all right. 7176-92135-0044 5.175 But it is the cigarette which chiefly has brought the modern drama to its present state of perfection. +4077-13754-0001 3.77 But a word further concerning the expedition in general. 4077-13751-0001 8.745 Its origin was small - a germ, an insignificant seed, hardly to be thought of as likely to arouse opposition. +4077-13754-0001 3.77 But a word further concerning the expedition in general. 4077-13751-0002 9.75 Instead of but six regularly affiliated members, and at most two score of adherents, the organization numbers today many hundred thousand souls. +4077-13751-0013 4.315 Their sufferings have never yet been fitly chronicled by human scribe. 4077-13751-0010 6.72 To the fervent Latter day Saint, a temple is not simply a church building, a house for religious assembly. +4077-13751-0019 2.92 Who began the quarrel? Was it the "Mormons"? 4077-13751-0013 4.315 Their sufferings have never yet been fitly chronicled by human scribe. +4077-13754-0001 3.77 But a word further concerning the expedition in general. 4077-13751-0017 5.095 Oh, what a record to read; what a picture to gaze upon; how awful the fact! +6930-81414-0019 3.38 Voltaire picked up something from the ground and looked at it. 6930-76324-0002 5.56 The poor little things"! cried Cynthia. "Think of them having been turned to the wall all these years! +6930-76324-0009 3.405 Do you suppose the miniature was a copy of the same thing"? 6930-76324-0004 6.15 But Joyce had not been listening. All at once she put down her candle on the table and faced her companion. +6930-76324-0009 3.405 Do you suppose the miniature was a copy of the same thing"? 6930-76324-0005 5.035 The twin brother did something she didn't like, and she turned his picture to the wall. +6930-76324-0001 3.2 They were certainly no nearer the solution of their problem. 6930-76324-0006 4.455 Hers happened to be in the same frame too, but she evidently didn't care about that. +6930-76324-0006 4.455 Hers happened to be in the same frame too, but she evidently didn't care about that. 6930-76324-0008 5.185 I thought we were 'stumped' again when I first saw that picture, but it's been of some use, after all. +6930-76324-0026 3.085 Isn't he the greatest for getting into odd corners"! 6930-76324-0011 9.24 They worry me terribly. And, besides, I'd like to see what this lovely furniture looks like without such quantities of dust all over it". "Good scheme, CYN"! +6930-76324-0006 4.455 Hers happened to be in the same frame too, but she evidently didn't care about that. 6930-76324-0012 4.655 We'll come in here this afternoon with old clothes on, and have a regular house cleaning! +6930-76324-0010 2.69 What in the world is that"? queried Joyce. 6930-76324-0013 4.305 It can't hurt anything, I'm sure, for we won't disturb things at all. +6930-76324-0007 2.82 Now what have you to say, Cynthia Sprague"? 6930-76324-0014 4.72 This thought, however, did not enter the heads of the enthusiastic pair. +6930-76324-0019 2.575 Now let's dust the furniture and pictures". 6930-76324-0016 9.205 The lure proved too much for him, and he came sporting after it, as friskily as a young kitten, much to Cynthia's delight when she caught sight of him. +6930-81414-0018 2.93 I remember saying. "Have we been together"? 6930-76324-0017 5.41 Oh, let him come along"! she urged. "I do love to see him about that old house. +6930-76324-0025 4.12 Why, it's Goliath as usual"! they both cried, peering in. 6930-76324-0020 6.315 Yet, little as it was, it had already made a vast difference in the aspect of the room. +6930-76324-0007 2.82 Now what have you to say, Cynthia Sprague"? 6930-76324-0021 7.355 Surface dust at least had been removed, and the fine old furniture gave a hint of its real elegance and polish. +6930-76324-0013 4.305 It can't hurt anything, I'm sure, for we won't disturb things at all. 6930-76324-0023 4.85 And my pocket money is getting low again, and you haven't any left, as usual. +6930-76324-0026 3.085 Isn't he the greatest for getting into odd corners"! 6930-76324-0024 4.05 They say illumination by candle light is the prettiest in the world. +6930-81414-0012 4.43 said another voice, which I recognized as Voltaire's. "Kaffar? 6930-76324-0025 4.12 Why, it's Goliath as usual"! they both cried, peering in. +6930-81414-0019 3.38 Voltaire picked up something from the ground and looked at it. 6930-76324-0027 8.27 Forgetting all their weariness, they seized their candles and scurried through the house, finding an occasional paper tucked away in some odd corner. +6930-81414-0018 2.93 I remember saying. "Have we been together"? 6930-76324-0028 9.875 Well, I'm convinced that the Boarded up House mystery happened not earlier than april sixteenth, eighteen sixty one, and probably not much later. +6930-76324-0007 2.82 Now what have you to say, Cynthia Sprague"? 6930-81414-0004 9.56 The story of its evil influence came back to me, and in my bewildered condition I wondered whether there was not some truth in what had been said. +6930-75918-0000 3.505 Concord returned to its place amidst the tents. 6930-81414-0006 6.8 What then? A human hand, large and shapely, appeared distinctly on the surface of the pond. +6930-75918-0011 3.195 I am convinced of what I say," said the count. 6930-81414-0007 4.365 Nothing more, not even the wrist to which it might be attached. +6930-75918-0013 2.94 In those very terms; I even added more. 6930-81414-0008 6.055 It did not beckon, or indeed move at all; it was as still as the hand of death. +6930-81414-0010 3.835 A sound of voices. A flash of light. 6930-81414-0011 4.7 A feeling of freedom, and I was awake! Where? +6930-76324-0025 4.12 Why, it's Goliath as usual"! they both cried, peering in. 6930-81414-0012 4.43 said another voice, which I recognized as Voltaire's. "Kaffar? +6930-81414-0007 4.365 Nothing more, not even the wrist to which it might be attached. 6930-81414-0013 7.325 I had scarcely known what I had been saying or doing up to this time, but as he spoke I looked at my hand. +6930-75918-0007 3.315 You will be frank with me"? "I always am". 6930-81414-0014 7.41 In the light of the moon I saw a knife red with blood, and my hand, too, was also discoloured. +6930-81414-0025 2.53 My position was too terrible. 6930-81414-0020 5 I say you do know what this means, and you must tell us". +6930-81414-0027 3.85 For some time after that I remembered nothing distinctly. 6930-81414-0022 4.34 I had again been acting under the influence of this man's power. +6930-81414-0021 3.225 A terrible thought flashed into my mind. 6930-81414-0023 4.885 Perchance, too, Kaffar's death might serve him in good stead. +6930-75918-0010 3.035 I can perceive love clearly enough". 6930-81414-0024 5.05 My tongue refused to articulate; my power of speech left me. +1221-135766-0015 2.63 If spoken to, she would not speak again. 1221-135766-0002 4.825 Yet these thoughts affected Hester Prynne less with hope than apprehension. +1221-135766-0015 2.63 If spoken to, she would not speak again. 1221-135766-0004 7.44 This outward mutability indicated, and did not more than fairly express, the various properties of her inner life. +1221-135766-0013 3.645 Pearl was a born outcast of the infantile world. 1221-135766-0007 8.795 Hester Prynne, nevertheless, the loving mother of this one child, ran little risk of erring on the side of undue severity. +1221-135767-0020 3.345 In truth, she seemed absolutely hidden behind it. 1221-135766-0014 4.75 Pearl saw, and gazed intently, but never sought to make acquaintance. +7021-79740-0012 3.26 said she, pointing to the playthings; "see! 7021-79730-0005 8.01 So you will be a good girl, I know, and not make any trouble, but will stay at home contentedly - won't you? +8463-294828-0021 2.735 A route slightly less direct, that's all. 8463-294825-0001 7.805 This reality begins to explain the dark power and otherworldly fascination of Twenty Thousand Leagues Under the Seas. +8463-287645-0014 3.02 of starting. I didn't know the way to come. 8463-294825-0003 9.935 Nemo builds a fabulous futuristic submarine, the Nautilus, then conducts an underwater campaign of vengeance against his imperialist oppressor. +8463-287645-0001 3.545 It is hardly necessary to say more of them here. 8463-294825-0005 7.7 Other subtleties occur inside each episode, the textures sparkling with wit, information, and insight. +8463-287645-0001 3.545 It is hardly necessary to say more of them here. 8463-294825-0010 4.580063 And in this last action he falls into the classic sin of Pride. +8463-287645-0009 3.71 I never knew of but one man who could ever please him. 8463-294825-0012 5.965063 The Nautilus nearly perishes in the Antarctic and Nemo sinks into a growing depression. +1580-141083-0021 3.715 There is no opening except the one pane," said our learned guide. 1580-141083-0000 8.94 I will endeavour, in my statement, to avoid such terms as would serve to limit the events to any particular place, or give a clue as to the people concerned. +1580-141084-0034 4.49 Well, well, don't trouble to answer. Listen, and see that I do you no injustice. 1580-141083-0002 6.135 My friend's temper had not improved since he had been deprived of the congenial surroundings of Baker Street. +1580-141083-0023 3.33 One could hardly hope for any upon so dry a day. 1580-141083-0003 6.55 Without his scrapbooks, his chemicals, and his homely untidiness, he was an uncomfortable man. +1580-141084-0003 4.1 No names, please"! said Holmes, as we knocked at Gilchrist's door. 1580-141083-0004 4.515 I had to read it over carefully, as the text must be absolutely correct. +1580-141084-0045 3.625 Suddenly he heard him at the very door. There was no possible escape. 1580-141083-0007 4.565 The moment I looked at my table, I was aware that someone had rummaged among my papers. +1580-141083-0011 2.825 A broken tip of lead was lying there also. 1580-141083-0008 4.305 The proof was in three long slips. I had left them all together. +1580-141083-0030 3.48 mister Soames was somewhat overwhelmed by this flood of information. 1580-141083-0009 7.04 The alternative was that someone passing had observed the key in the door, had known that I was out, and had entered to look at the papers. +1580-141083-0030 3.48 mister Soames was somewhat overwhelmed by this flood of information. 1580-141083-0010 5.32 I gave him a little brandy and left him collapsed in a chair, while I made a most careful examination of the room. +1580-141083-0050 3.085 I really don't think he knew much about it, mister Holmes. 1580-141083-0012 7.065 Not only this, but on the table I found a small ball of black dough or clay, with specks of something which looks like sawdust in it. +1580-141083-0019 2.705 Above were three students, one on each story. 1580-141083-0013 4.32 Above all things, I desire to settle the matter quietly and discreetly". +1580-141083-0048 2.785 How came you to leave the key in the door"? 1580-141083-0015 4.985 Did anyone know that these proofs would be there"? "No one save the printer". +1580-141084-0021 4.01 On the palm were three little pyramids of black, doughy clay. 1580-141083-0016 4.255 I was in such a hurry to come to you". "You left your door open"? +1580-141083-0036 3.98 Holmes held it out on his open palm in the glare of the electric light. 1580-141083-0020 5.135 Then he approached it, and, standing on tiptoe with his neck craned, he looked into the room. +1580-141084-0050 2.78 If mister Soames saw them, the game was up. 1580-141083-0024 4.48 You left him in a chair, you say. Which chair"? "By the window there". +1580-141084-0037 2.965 When I approached your room, I examined the window. 1580-141083-0026 4.775 As a matter of fact, he could not," said Soames, "for I entered by the side door". +1580-141083-0030 3.48 mister Soames was somewhat overwhelmed by this flood of information. 1580-141083-0027 5.225 How long would it take him to do that, using every possible contraction? A quarter of an hour, not less. +1580-141084-0050 2.78 If mister Soames saw them, the game was up. 1580-141083-0031 6.25 Holmes held out a small chip with the letters NN and a space of clear wood after them. "You see"? +1580-141084-0036 2.475 The Indian I also thought nothing of. 1580-141083-0032 4.135 Watson, I have always done you an injustice. There are others. +1580-141084-0045 3.625 Suddenly he heard him at the very door. There was no possible escape. 1580-141083-0033 7.45 I was hoping that if the paper on which he wrote was thin, some trace of it might come through upon this polished surface. No, I see nothing. +1580-141083-0025 3.905 The man entered and took the papers, sheet by sheet, from the central table. 1580-141083-0034 6.99 As Holmes drew the curtain I was aware, from some little rigidity and alertness of his attitude, that he was prepared for an emergency. +1580-141084-0050 2.78 If mister Soames saw them, the game was up. 1580-141083-0035 4.98 Holmes turned away, and stooped suddenly to the floor. "Hello! What's this"? +1580-141083-0030 3.48 mister Soames was somewhat overwhelmed by this flood of information. 1580-141083-0037 5.73 What could he do? He caught up everything which would betray him, and he rushed into your bedroom to conceal himself". +1580-141083-0036 3.98 Holmes held it out on his open palm in the glare of the electric light. 1580-141083-0038 7.535 I understand you to say that there are three students who use this stair, and are in the habit of passing your door"? "Yes, there are". +1580-141083-0024 4.48 You left him in a chair, you say. Which chair"? "By the window there". 1580-141083-0042 5.865 My scholar has been left very poor, but he is hard working and industrious. He will do well. +1580-141084-0014 3.97 Why, Bannister, the servant. What's his game in the matter"? 1580-141083-0044 5.505 I dare not go so far as that. But, of the three, he is perhaps the least unlikely". +1580-141083-0025 3.905 The man entered and took the papers, sheet by sheet, from the central table. 1580-141083-0045 4.36 He was still suffering from this sudden disturbance of the quiet routine of his life. +1580-141083-0052 3.45 Oh, I would not venture to say, sir. 1580-141083-0053 4.015 You haven't seen any of them"? "No, sir". +4992-41797-0003 2.835 mister Popham laid down his brush. 4992-41797-0000 5.485 Yes, dead these four years, an' a good job for her, too. +4992-41797-0003 2.835 mister Popham laid down his brush. 4992-41797-0002 5.625 Grandfather was Alexander Carey, L L. D., - Doctor of Laws, that is". +4992-23283-0016 4.495 Again he searched his own thoughts; nor ineffectually as before. 4992-41797-0004 7.315 I swan to man"! he ejaculated. "If you don't work hard you can't keep up with the times! Doctor of Laws! +4992-23283-0015 3.675 Is she not afraid that I will thwart her inclinations"? 4992-41797-0006 4.55 He keeps the thou shalt not commandments first rate, Hen Lord does! +4992-23283-0015 3.675 Is she not afraid that I will thwart her inclinations"? 4992-41797-0007 6.905 He give up his position and shut the family up in that tomb of a house so 't he could study his books. +4992-41797-0012 2.705 She is wild to know how to do things. 4992-41797-0008 8.965 mister Popham exaggerated nothing, but on the contrary left much unsaid in his narrative of the family at the House of Lords. +4992-41797-0003 2.835 mister Popham laid down his brush. 4992-41797-0010 6.82 Always irritable, cold, indifferent, he had grown rapidly more so as years went on. +4992-41797-0016 3.3 They couldn't run nor move; they're just pasteboard". 4992-41797-0011 5.445 Whatever appealed to her sense of beauty was straightway transferred to paper or canvas. +4992-41806-0009 4.355 exclaimed Bill Harmon to his wife as they went through the lighted hall. 4992-41797-0013 9.8 She makes effort after effort, trembling with eagerness, and when she fails to reproduce what she sees, she works herself into a frenzy of grief and disappointment". +4992-41806-0009 4.355 exclaimed Bill Harmon to his wife as they went through the lighted hall. 4992-41797-0014 7.215 When she could not make a rabbit or a bird look "real" on paper, she searched in her father's books for pictures of its bones. +4992-41806-0009 4.355 exclaimed Bill Harmon to his wife as they went through the lighted hall. 4992-41797-0015 8.65 Cyril, there must be some better way of doing; I just draw the outline of an animal and then I put hairs or feathers on it. They have no bodies. +4992-23283-0011 4.225 If she does not know how to estimate her own value, I do. 4992-41797-0017 8.69 He wouldn't search, so don't worry," replied Cyril quietly, and the two looked at each other and knew that it was so. +4992-23283-0016 4.495 Again he searched his own thoughts; nor ineffectually as before. 4992-41797-0018 9.155 There, in the cedar hollow, then, lived Olive Lord, an angry, resentful, little creature weighed down by a fierce sense of injury. +4992-23283-0001 2.71 Miss Milner's health is not good"! 4992-41797-0019 4.755 Olive's mournful black eyes met Nancy's sparkling brown ones. +4992-41797-0012 2.705 She is wild to know how to do things. 4992-41797-0020 7.49 Nancy's curly chestnut crop shone in the sun, and Olive's thick black plaits looked blacker by contrast. +4992-23283-0007 4.045 To ask any more questions of you, I believe, would be unfair. 4992-41797-0021 8.23 She's wonderful! More wonderful than anybody we've ever seen anywhere, and she draws better than the teacher in Charlestown! +4992-23283-0001 2.71 Miss Milner's health is not good"! 4992-41797-0022 6.45 She's older than I am, but so tiny and sad and shy that she seems like a child. +2830-3980-0001 3.945 They said to the Galatians: "You have no right to think highly of Paul. 2830-3979-0000 6.12 We want you to help us publish some leading work of Luther's for the general American market. Will you do it"? +2830-3980-0020 3.46 This is no sinful pride. It is holy pride. 2830-3979-0002 4.315 Let us begin with that: his Commentary on Galatians..". +2830-3980-0046 2.84 Was it not enough to say, "from God the Father"? 2830-3979-0003 8.085 The undertaking, which seemed so attractive when viewed as a literary task, proved a most difficult one, and at times became oppressive. +2830-3980-0012 3.42 The most they could claim is that they were sent by others. 2830-3979-0006 4.55 A word should now be said about the origin of Luther's Commentary on Galatians. +2830-3980-0013 4.145 He mentions the apostles first because they were appointed directly by God. 2830-3979-0008 9.44 In other words, these three men took down the lectures which Luther addressed to his students in the course of Galatians, and Roerer prepared the manuscript for the printer. +2830-3980-0013 4.145 He mentions the apostles first because they were appointed directly by God. 2830-3979-0009 8.35 It presents like no other of Luther's writings the central thought of Christianity, the justification of the sinner for the sake of Christ's merits alone. +2830-3980-0020 3.46 This is no sinful pride. It is holy pride. 2830-3979-0011 9.45 The Lord who has given us power to teach and to hear, let Him also give us the power to serve and to do". LUKE two +2094-142345-0025 3.595 Cold, is it, my darling? Bless your sweet face"! 2094-142345-0001 8.03 But the windows are patched with wooden panes, and the door, I think, is like the gate it is never opened. +2094-142345-0025 3.595 Cold, is it, my darling? Bless your sweet face"! 2094-142345-0005 9.09 Several clothes horses, a pillion, a spinning wheel, and an old box wide open and stuffed full of coloured rags. +2094-142345-0060 2.71 Oh, I've no doubt it's in capital order. 2094-142345-0021 5.335 That's the way with you that's the road you'd all like to go, headlongs to ruin. +2094-142345-0018 3.155 Who taught you to scrub a floor, I should like to know? 2094-142345-0034 7.99 And there's linen in the house as I could well spare you, for I've got lots o' sheeting and table clothing, and towelling, as isn't made up. +2094-142345-0026 2.825 She's going to put the ironing things away". 2094-142345-0036 6.915 Nay, dear aunt, you never heard me say that all people are called to forsake their work and their families. +2094-142345-0020 2.435 That's what you'd like to be doing, is it? 2094-142345-0039 6.28 I've strong assurance that no evil will happen to you and my uncle and the children from anything I've done. +2094-142345-0020 2.435 That's what you'd like to be doing, is it? 2094-142345-0043 7.35 By this time the two gentlemen had reached the palings and had got down from their horses: it was plain they meant to come in. +2094-142345-0032 3.24 I often heard her talk of you in the same sort of way. 2094-142345-0048 6.39 said Captain Donnithorne, seating himself where he could see along the short passage to the open dairy door. +2094-142345-0004 2.64 And what through the left hand window? 2094-142345-0049 6.125 No, sir, he isn't; he's gone to Rosseter to see mister West, the factor, about the wool. +2094-142345-0018 3.155 Who taught you to scrub a floor, I should like to know? 2094-142345-0051 5.31 No, thank you; I'll just look at the whelps and leave a message about them with your shepherd. +2094-142345-0060 2.71 Oh, I've no doubt it's in capital order. 2094-142345-0052 6.53 I must come another day and see your husband; I want to have a consultation with him about horses. +1995-1837-0009 3.76 The lagoon had been level with the dykes a week ago; and now? 1995-1836-0001 6 At last the Cotton Combine was to all appearances an assured fact and he was slated for the Senate. +1995-1837-0015 4.485 The squares of cotton, sharp edged, heavy, were just about to burst to bolls! 1995-1836-0003 7.965 She was not herself a notably intelligent woman; she greatly admired intelligence or whatever looked to her like intelligence in others. +1995-1837-0015 4.485 The squares of cotton, sharp edged, heavy, were just about to burst to bolls! 1995-1836-0006 7.715 She was therefore most agreeably surprised to hear mister Cresswell express himself so cordially as approving of Negro education. +1995-1837-0005 2.635 She was so strange and human a creature. 1995-1836-0008 6.985 I believe in the training of people to their highest capacity". The Englishman here heartily seconded him. +1995-1837-0000 3.865 He knew the Silver Fleece - his and Zora's - must be ruined. 1995-1836-0009 6.71 But," Cresswell added significantly, "capacity differs enormously between races". +1995-1826-0004 3.035 Might learn something useful down there". 1995-1836-0011 4.705 Positively heroic," added Cresswell, avoiding his sister's eyes. +1995-1837-0022 3.415 Up in the sick room Zora lay on the little white bed. 1995-1836-0014 9.045 Fortunately," said mister Vanderpool, "Northerners and Southerners are arriving at a better mutual understanding on most of these matters". +237-126133-0021 4.365 she asked impulsively, "I didn't believe you could persuade her, father". 237-126133-0003 6.56 Somehow, of all the days when the home feeling was the strongest, this day it seemed as if she could bear it no longer. +237-126133-0025 3.755 At last he came out of them, and wiped his face vigorously. 237-126133-0005 6.51 Oh, she's always at the piano," said Van. "She must be there now, somewhere," and then somebody laughed. +237-126133-0016 4.25 Oh no, Jasper; I must go by my very own self". 237-126133-0006 6.15 At this, the bundle opened suddenly, and - out popped Phronsie! +237-126133-0021 4.365 she asked impulsively, "I didn't believe you could persuade her, father". 237-126133-0007 8.68 But Polly couldn't speak; and if Jasper hadn't caught her just in time, she would have tumbled over backward from the stool, Phronsie and all! +237-126133-0025 3.755 At last he came out of them, and wiped his face vigorously. 237-126133-0010 6.24 Oh, you are the dearest and best mister King I ever saw! but how did you make mammy let her come"? +237-126133-0009 3.97 Now you'll stay," cried Van; "say, Polly, won't you". 237-126133-0011 6.71 Isn't he splendid"! cried Jasper in intense pride, swelling up. "Father knew how to do it". +237-126133-0018 4.095 Don't mind it, Polly," whispered Jasper; "twasn't her fault". 237-126133-0012 4.45 There, there," he said soothingly, patting her brown, fuzzy head. +237-126133-0016 4.25 Oh no, Jasper; I must go by my very own self". 237-126133-0013 6.815 I know," gasped Polly, controlling her sobs; "I won't - only - I can't thank you"! +237-126133-0025 3.755 At last he came out of them, and wiped his face vigorously. 237-126133-0014 6.79 asked Phronsie in intense interest slipping down out of Polly's arms, and crowding up close to Jasper's side. +237-126133-0025 3.755 At last he came out of them, and wiped his face vigorously. 237-126133-0015 9.34 Yes, all alone by himself," asserted Jasper, vehemently, and winking furiously to the others to stop their laughing; "he did now, truly, Phronsie". +237-126133-0009 3.97 Now you'll stay," cried Van; "say, Polly, won't you". 237-126133-0016 4.25 Oh no, Jasper; I must go by my very own self". +237-126133-0021 4.365 she asked impulsively, "I didn't believe you could persuade her, father". 237-126133-0017 6.21 There Jap, you've caught it," laughed Percy; while the others screamed at the sight of Jasper's face. +237-126133-0008 3.865 asked Phronsie, with her little face close to Polly's own. 237-126133-0018 4.095 Don't mind it, Polly," whispered Jasper; "twasn't her fault". +237-126133-0025 3.755 At last he came out of them, and wiped his face vigorously. 237-126133-0019 7.12 Dear me"! ejaculated the old gentleman, in the utmost amazement; "and such a time as I've had to get her here too"! +237-126133-0025 3.755 At last he came out of them, and wiped his face vigorously. 237-126133-0021 4.365 she asked impulsively, "I didn't believe you could persuade her, father". +237-126133-0021 4.365 she asked impulsively, "I didn't believe you could persuade her, father". 237-126133-0022 5.04 I didn't have any fears, if I worked it rightly," said the old gentleman complacently. +237-126133-0021 4.365 she asked impulsively, "I didn't believe you could persuade her, father". 237-126133-0023 6.675 he cried in high dudgeon; just as if he owned the whole of the Peppers, and could dispose of them all to suit his fancy! +237-126133-0021 4.365 she asked impulsively, "I didn't believe you could persuade her, father". 237-126133-0024 9.665 And the old gentleman was so delighted with his success, that he had to burst out into a series of short, happy bits of laughter, that occupied quite a space of time. +4507-16021-0040 3.925 One thinks one hears hydras talking. 4507-16021-0003 4.895 She has a son, theft, and a daughter, hunger. +4507-16021-0012 2.735 Why should one halt on the way? 4507-16021-0005 4.21 We have never understood this sort of objections. +4507-16021-0015 3.86 Since when has malady banished medicine? 4507-16021-0011 5.615 Why should one not explore everything, and study everything? +4507-16021-0000 2.59 Chapter one Origin. 4507-16021-0014 6.115 Now, when has horror ever excluded study? +4507-16021-0007 2.63 Slang makes one shudder"! 4507-16021-0024 5.14 Algebra, medicine, botany, have each their slang. +4507-16021-0041 2.975 It is unintelligible in the dark. 4507-16021-0025 9.215 To meet the needs of this conflict, wretchedness has invented a language of combat, which is slang. +4507-16021-0050 3.895 And you belong to that small class who are happy! 4507-16021-0033 5.545 Do we really know the mountain well when we are not acquainted with the cavern? +4507-16021-0058 3.11 The flame is the enemy of the wing. 4507-16021-0035 7.535 True history being a mixture of all things, the true historian mingles in everything. +4507-16021-0028 3.265 Even dialect, let that pass! 4507-16021-0036 5.435 Facts form one of these, and ideas the other. +4507-16021-0015 3.86 Since when has malady banished medicine? 4507-16021-0037 5.35 There it clothes itself in word masks, in metaphor rags. +4507-16021-0050 3.895 And you belong to that small class who are happy! 4507-16021-0045 4.89 It is so made, that everywhere we feel the sense of punishment. +4507-16021-0012 2.735 Why should one halt on the way? 4507-16021-0046 4.59 Each day has its own great grief or its little care. +4507-16021-0050 3.895 And you belong to that small class who are happy! 4507-16021-0048 5.215 This without reckoning in the pains of the heart. And so it goes on. +4507-16021-0050 3.895 And you belong to that small class who are happy! 4507-16021-0049 5.91 There is hardly one day out of a hundred which is wholly joyous and sunny. +4507-16021-0019 2.93 It is the language of wretchedness. 4507-16021-0051 6.17 In this world, evidently the vestibule of another, there are no fortunate. +4507-16021-0007 2.63 Slang makes one shudder"! 4507-16021-0052 6.275 The real human division is this: the luminous and the shady. +4507-16021-0005 4.21 We have never understood this sort of objections. 4507-16021-0053 8.095 To diminish the number of the shady, to augment the number of the luminous,-that is the object. +4507-16021-0029 3.87 To this we reply in one word, only. 4507-16021-0054 4.315 That is why we cry: Education! science! +4507-16021-0041 2.975 It is unintelligible in the dark. 4507-16021-0055 7.225 To teach reading, means to light the fire; every syllable spelled out sparkles. +4507-16021-0040 3.925 One thinks one hears hydras talking. 4507-16021-0056 6.345 However, he who says light does not, necessarily, say joy. +4507-16021-0038 3.885 In this guise it becomes horrible. 4507-16021-0057 4.61 People suffer in the light; excess burns. +4507-16021-0015 3.86 Since when has malady banished medicine? 4507-16021-0059 6.205 To burn without ceasing to fly, therein lies the marvel of genius. +8555-284447-0020 4.09 The goat's warlike spirit was roused by this successful attack. 8555-284447-0000 9.605 Then he rushed down stairs into the courtyard, shouting loudly for his soldiers and threatening to patch everybody in his dominions if the sailorman was not recaptured. +8555-284447-0003 4.415 But Captain Bill made no such attempt, knowing it would be useless. 8555-284447-0001 8.61 Hold him fast, my men, and as soon as I've had my coffee and oatmeal I'll take him to the Room of the Great Knife and patch him". +8555-284447-0022 3.56 I had a notion it was you, mate, as saved me from the knife. 8555-284447-0002 8.025 I wouldn't mind a cup of coffee myself," said Captain Bill. "I've had considerable exercise this morning and I'm all ready for breakfast". +8555-284447-0009 3.275 Mornin', girls; hope ye feel as well as ye look". 8555-284447-0003 4.415 But Captain Bill made no such attempt, knowing it would be useless. +8555-284447-0020 4.09 The goat's warlike spirit was roused by this successful attack. 8555-284447-0004 5.485 As soon as they entered the Room of the Great Knife the Boolooroo gave a yell of disappointment. +8555-284447-0020 4.09 The goat's warlike spirit was roused by this successful attack. 8555-284447-0005 6.83 The Room of the Great Knife was high and big, and around it ran rows of benches for the spectators to sit upon. +8555-284449-0005 2.555 When he finished she said cheerfully: 8555-284447-0007 6.365 Therefore her Majesty paid no attention to anyone and no one paid any attention to her. +8555-284447-0020 4.09 The goat's warlike spirit was roused by this successful attack. 8555-284447-0008 8.39 Rich jewels of blue stones glittered upon their persons and the royal ladies were fully as gorgeous as they were haughty and overbearing. +8555-284447-0020 4.09 The goat's warlike spirit was roused by this successful attack. 8555-284447-0013 9.04 Why, you said to fetch the first living creature we met, and that was this billygoat," replied the Captain, panting hard as he held fast to one of the goat's horns. +8555-284447-0020 4.09 The goat's warlike spirit was roused by this successful attack. 8555-284447-0014 8.47 The idea of patching Captain Bill to a goat was vastly amusing to him, and the more he thought of it the more he roared with laughter. +8555-284447-0020 4.09 The goat's warlike spirit was roused by this successful attack. 8555-284447-0018 5.46 At once the goat gave a leap, escaped from the soldiers and with bowed head rushed upon the Boolooroo. +8555-284447-0003 4.415 But Captain Bill made no such attempt, knowing it would be useless. 8555-284447-0020 4.09 The goat's warlike spirit was roused by this successful attack. +8555-284447-0022 3.56 I had a notion it was you, mate, as saved me from the knife. 8555-284447-0023 7.155 I couldn't shiver much, bein' bound so tight, but when I'm loose I mean to have jus' one good shiver to relieve my feelin's". +8555-292519-0013 4.185 That was but rustling of dripping plants in the dark. 8555-284447-0024 4.635 Come and get the Boolooroo," she said, going toward the benches. +8230-279154-0003 3.195 And what sort of evidence is logically possible? 8230-279154-0000 8.805 The analysis of knowledge will occupy us until the end of the thirteenth lecture, and is the most difficult part of our whole enterprise. +8230-279154-0032 3.88 It is this that is of interest to theory of knowledge. 8230-279154-0005 7.72 All that I am doing is to use its logical tenability as a help in the analysis of what occurs when we remember. +8230-279154-0003 3.195 And what sort of evidence is logically possible? 8230-279154-0006 7.51 The behaviourist, who attempts to make psychology a record of behaviour, has to trust his memory in making the record. +8230-279154-0008 3.62 But I do not think such an inference is warranted. 8230-279154-0011 6.25 Some images, like some sensations, feel very familiar, while others feel strange. +8230-279154-0003 3.195 And what sort of evidence is logically possible? 8230-279154-0014 7.94 I come now to the other characteristic which memory images must have in order to account for our knowledge of the past. +8230-279154-0003 3.195 And what sort of evidence is logically possible? 8230-279154-0015 8.05 They must have some characteristic which makes us regard them as referring to more or less remote portions of the past. +8230-279154-0003 3.195 And what sort of evidence is logically possible? 8230-279154-0017 7.93 There may be a specific feeling which could be called the feeling of "pastness," especially where immediate memory is concerned. +8230-279154-0003 3.195 And what sort of evidence is logically possible? 8230-279154-0020 7.835 If we had retained the "subject" or "act" in knowledge, the whole problem of memory would have been comparatively simple. +8230-279154-0003 3.195 And what sort of evidence is logically possible? 8230-279154-0021 6.56 Remembering has to be a present occurrence in some way resembling, or related to, what is remembered. +8230-279154-0012 3.64 Familiarity is a feeling capable of degrees. 8230-279154-0022 6.44 Some points may be taken as fixed, and such as any theory of memory must arrive at. +8230-279154-0032 3.88 It is this that is of interest to theory of knowledge. 8230-279154-0023 6.265 In this case, as in most others, what may be taken as certain in advance is rather vague. +8230-279154-0008 3.62 But I do not think such an inference is warranted. 8230-279154-0024 6.34 The first of our vague but indubitable data is that there is knowledge of the past. +8230-279154-0032 3.88 It is this that is of interest to theory of knowledge. 8230-279154-0026 9.3 This distinction is vital to the understanding of memory. But it is not so easy to carry out in practice as it is to draw in theory. +8230-279154-0003 3.195 And what sort of evidence is logically possible? 8230-279154-0029 8.54 The fact that a man can recite a poem does not show that he remembers any previous occasion on which he has recited or read it. +8230-279154-0008 3.62 But I do not think such an inference is warranted. 8230-279154-0030 7.28 Semon's two books, mentioned in an earlier lecture, do not touch knowledge memory at all closely. +8230-279154-0012 3.64 Familiarity is a feeling capable of degrees. 8230-279154-0035 7.555 Thus no knowledge as to the past is to be derived from the feeling of familiarity alone. +8230-279154-0003 3.195 And what sort of evidence is logically possible? 8230-279154-0039 4.59 This knowledge is memory in one sense, though in another it is not. +7021-85628-0000 3.02 But Anders cared nothing about that. 7021-79740-0001 5.995 Della had a young sister named Maria, and a cousin whose name was Jane. +7021-85628-0019 3.255 With one jump Anders got out of his chair. 7021-79740-0002 9.225 Now Delia contrived to obtain a great influence and ascendency over the minds of the children by means of these dolls. +7021-79740-0009 3.635 They were now playing with their dolls in the parlor. 7021-79740-0003 4.985 To give an idea of these conversations I will report one of them in full. +7021-79740-0012 3.26 said she, pointing to the playthings; "see! 7021-79740-0004 6.465 You have come, Andella (Andella was the name of Jane's doll), to make Rosalie a visit. +7021-85628-0019 3.255 With one jump Anders got out of his chair. 7021-79740-0006 5.965 I expect you have been a very good girl, Andella, since you were here last". +7021-79740-0012 3.26 said she, pointing to the playthings; "see! 7021-79740-0007 6.99 Then, turning to Jane, she asked, in a somewhat altered tone, "Has she been a good girl, Jane"? +7021-79740-0009 3.635 They were now playing with their dolls in the parlor. 7021-79740-0013 7.365 Put these playthings all away quick, and carefully, and we will not let them know any thing about your leaving them out". +61-70970-0016 4.37 We will go out together to the bower; there is a way down to the court from my window. 61-70968-0000 4.905 He began a confused complaint against the wizard, who had vanished behind the curtain on the left. +61-70968-0012 2.61 Cries of: "A Nottingham! A Nottingham"! 61-70968-0003 4.315 He was like unto my father, in a way, and yet was not my father. +61-70970-0009 3.405 Tis late; and I go myself within a short space. 61-70968-0005 5.07 This was so sweet a lady, sir, and in some manner I do think she died. +61-70968-0018 2.405 So I did push this fellow". 61-70968-0009 4.51 Like as not, young master, though I am an old man". +61-70970-0033 3.42 Truly such a horse should be worth much in Nottingham Fair! 61-70968-0010 8.295 Forthwith all ran to the opening of the tent to see what might be amiss; but Master Will, who peeped out first, needed no more than one glance. +61-70970-0016 4.37 We will go out together to the bower; there is a way down to the court from my window. 61-70968-0011 6.375 He gave way to the others very readily and retreated unperceived by the Squire and Mistress Fitzooth to the rear of the tent. +61-70970-0019 3.78 At last all was quiet and black in the courtyard of Gamewell. 61-70968-0013 4.45 Before them fled the stroller and his three sons, capless and terrified. +61-70968-0006 2.935 But then the picture was gone as quickly as it came". 61-70968-0014 7.485 What is the tumult and rioting"? cried out the Squire, authoritatively, and he blew twice on a silver whistle which hung at his belt. +61-70968-0036 2.934938 George Montfichet will never forget this day. 61-70968-0015 5.375 Nay, we refused their request most politely, most noble," said the little stroller. +61-70970-0007 4.485 He was in deep converse with the clerk, and entered the hall holding him by the arm. 61-70968-0017 5.11 I could not see my boy injured, excellence, for but doing his duty as one of Cumberland's sons. +61-70970-0023 3.705 Be not so foolish, friend," said Fitzooth, crossly. 61-70968-0019 5.475 It is enough," said George Gamewell, sharply, and he turned upon the crowd. +61-70968-0025 4.41 Come to me, men, here, here"! He raised his voice still louder. 61-70968-0020 5.105 Shame on you, citizens," cried he; "I blush for my fellows of Nottingham. +61-70968-0048 3.02 And Henry might return to England at any moment. 61-70968-0022 4.67 Tis fine for you to talk, old man," answered the lean, sullen apprentice. +61-70970-0033 3.42 Truly such a horse should be worth much in Nottingham Fair! 61-70968-0023 5.025 But I wrestled with this fellow and do know that he played unfairly in the second bout. +61-70970-0032 3.135 enquired Robin, with his suspicions still upon him. 61-70968-0024 6.025 spoke the Squire, losing all patience; "and it was to you that I gave another purse in consolation! +61-70970-0003 3.835 If, for a whim, you beggar yourself, I cannot stay you. 61-70968-0025 4.41 Come to me, men, here, here"! He raised his voice still louder. +61-70970-0040 4.165 They regained their apartment, apparently without disturbing the household of Gamewell. 61-70968-0026 4.92 The strollers took their part in it with hearty zest now that they had some chance of beating off their foes. +61-70970-0016 4.37 We will go out together to the bower; there is a way down to the court from my window. 61-70968-0027 6.87 Robin and the little tumbler between them tried to force the Squire to stand back, and very valiantly did these two comport themselves. +61-70968-0046 3.55 Nottingham Castle was reached, and admittance was demanded. 61-70968-0030 5.685 Now, be silent, on your lives," he began; but the captured apprentice set up an instant shout. +61-70968-0029 3.495 The Squire helped to thrust them all in and entered swiftly himself. 61-70968-0032 4.28 He felt for and found the wizard's black cloth. The Squire was quite out of breath. +61-70970-0016 4.37 We will go out together to the bower; there is a way down to the court from my window. 61-70968-0033 5.685 Thrusting open the proper entrance of the tent, Robin suddenly rushed forth with his burden, with a great shout. +61-70970-0016 4.37 We will go out together to the bower; there is a way down to the court from my window. 61-70968-0035 7.95 Taking advantage of this, the Squire's few men redoubled their efforts, and, encouraged by Robin's and the little stroller's cries, fought their way to him. +61-70968-0036 2.934938 George Montfichet will never forget this day. 61-70968-0037 4.315 What is your name, lording"? asked the little stroller, presently. +61-70970-0022 3.97 Robin entered the hut, dragging the unwilling esquire after him. 61-70968-0041 6.825 I like you, Will; you are the second Will that I have met and liked within two days; is there a sign in that"? +61-70968-0003 4.315 He was like unto my father, in a way, and yet was not my father. 61-70968-0043 6.735 Friends," said Montfichet, faintly, to the wrestlers, "bear us escort so far as the Sheriff's house. +61-70970-0013 4.35 There was no chance to alter his sleeping room to one nearer to Gamewell's chamber. 61-70968-0047 4.775 Master Monceux, the Sheriff of Nottingham, was mightily put about when told of the rioting. +61-70968-0048 3.02 And Henry might return to England at any moment. 61-70968-0049 8.25 Have your will, child, if the boy also wills it," Montfichet answered, feeling too ill to oppose anything very strongly just then. +61-70968-0042 2.785 Montfichet called out for Robin to give him an arm. 61-70968-0050 5.58 He made an effort to hide his condition from them all, and Robin felt his fingers tighten upon his arm. +61-70970-0030 3.24 Save me, masters, but you startled me rarely"! 61-70968-0053 4.22 He is my esquire, excellency," returned Robin, with dignity. +61-70970-0040 4.165 They regained their apartment, apparently without disturbing the household of Gamewell. 61-70968-0054 7.86 Mistress Fitzooth had been carried off by the Sheriff's daughter and her maids as soon as they had entered the house, so that Robin alone had the care of Montfichet. +61-70968-0012 2.61 Cries of: "A Nottingham! A Nottingham"! 61-70968-0057 5.065 These escapades are not for old Gamewell, lad; his day has come to twilight. +61-70968-0048 3.02 And Henry might return to England at any moment. 61-70968-0061 5.53 You are a worthy leech, Will," presently whispered Robin. "The wine has worked a marvel. +8224-274384-0003 3.87 or hath he given us any gift? 8224-274384-0002 9.815 They informed the English parliament of this unexpected incident, and assured them that they had entered into no private treaty with the king. +8224-274384-0003 3.87 or hath he given us any gift? 8224-274384-0005 8.745 Another preacher, after reproaching him to his face with his misgovernment, ordered this psalm to be sung: +8224-274384-0003 3.87 or hath he given us any gift? 8224-274384-0006 6.81 The king stood up, and called for that psalm which begins with these words, +8224-274384-0003 3.87 or hath he given us any gift? 8224-274384-0007 6.23 Have mercy, Lord, on me, I pray; For men would me devour". +8224-274384-0003 3.87 or hath he given us any gift? 8224-274384-0009 4.805 The parliament and the Scots laid their proposals before the king. +8224-274384-0003 3.87 or hath he given us any gift? 8224-274384-0013 5.44 His death, in this conjuncture, was a public misfortune. +6829-68771-0035 4.39 Will you leave me alone in my own room, or must I go away to escape you"? 6829-68769-0001 9.315 It was a serious crime indeed, mister Watson told them, and Tom Gates bade fair to serve a lengthy term in state's prison as a consequence of his rash act. +6829-68769-0046 2.57 You're foolish. Why should you do all this"? 6829-68769-0003 4.215 It was a deliberate theft from his employers to protect a girl he loved. +6829-68769-0007 3.865 But under the circumstances I doubt if such an arrangement could be made". 6829-68769-0004 7.145 But they could not have proven a case against Lucy, if she was innocent, and all their threats of arresting her were probably mere bluff. +6829-68769-0044 3.225 It has cost me twice sixty dollars in annoyance". 6829-68769-0005 6.72 He was soft hearted and impetuous," said Beth; "and, being in love, he didn't stop to count the cost". +6829-68769-0022 4.115 We have heard something of your story," said Kenneth, "and are interested in it. 6829-68769-0006 7.195 If the prosecution were withdrawn and the case settled with the victim of the forged check, then the young man would be allowed his freedom. +6829-68769-0022 4.115 We have heard something of your story," said Kenneth, "and are interested in it. 6829-68769-0009 4.22 They were received in the little office by a man named Markham, who was the jailer. +6829-68769-0003 4.215 It was a deliberate theft from his employers to protect a girl he loved. 6829-68769-0011 4.685 I'm running for Representative on the Republican ticket," said Kenneth, quietly. +6829-68769-0039 4.045 He looked up rather ungraciously, but motioned them to be seated. 6829-68769-0012 4.295 Oh, say! that's different," observed Markham, altering his demeanor. +6829-68769-0003 4.215 It was a deliberate theft from his employers to protect a girl he loved. 6829-68769-0015 6.525 Sometimes I'm that yearning for a smoke I'm nearly crazy, an' I don't know which is worst, dying one way or another. +6829-68769-0037 2.53 I've seen lots of that kind in my day. 6829-68769-0016 4.12 He unlocked the door, and called: "Here's visitors, Tom". +6829-68771-0028 3.555 She even seemed mildly amused at the attention she attracted. 6829-68769-0020 5.125 Sit down, please," said Gates, in a cheerful and pleasant voice. "There's a bench here". +6829-68769-0002 3.075 I can't see it in that light," said the old lawyer. 6829-68769-0021 7.895 A fresh, wholesome looking boy, was Tom Gates, with steady gray eyes, an intelligent forehead, but a sensitive, rather weak mouth. +6829-68769-0009 4.22 They were received in the little office by a man named Markham, who was the jailer. 6829-68769-0022 4.115 We have heard something of your story," said Kenneth, "and are interested in it. +6829-68771-0028 3.555 She even seemed mildly amused at the attention she attracted. 6829-68769-0023 4.89 I didn't stop to think whether it was foolish or not. I did it; and I'm glad I did". +6829-68769-0007 3.865 But under the circumstances I doubt if such an arrangement could be made". 6829-68769-0025 5.735 Then Rogers wouldn't do anything but lead her around, and wait upon her, and the place went to rack and ruin". +6829-68769-0051 3.545 There was a grim smile of amusement on his shrewd face. 6829-68769-0026 4.64 He spoke simply, but paced up and down the narrow cell in front of them. +6829-68769-0012 4.295 Oh, say! that's different," observed Markham, altering his demeanor. 6829-68769-0030 4.91 I was bookkeeper, so it was easy to get a blank check and forge the signature. +6829-68769-0037 2.53 I've seen lots of that kind in my day. 6829-68769-0031 5.555 As regards my robbing the company, I'll say that I saved them a heavy loss one day. +6829-68769-0007 3.865 But under the circumstances I doubt if such an arrangement could be made". 6829-68769-0032 5.72 I discovered and put out a fire that would have destroyed the whole plant. But Marshall never even thanked me. +6829-68769-0019 2.665 Sorry we haven't any reception room in the jail. 6829-68769-0033 4.02 It was better for him to think the girl unfeeling than to know the truth. +6829-68769-0019 2.665 Sorry we haven't any reception room in the jail. 6829-68769-0034 6.055 I'm going to see mister Marshall," said Kenneth, "and discover what I can do to assist you". "Thank you, sir. +6829-68771-0035 4.39 Will you leave me alone in my own room, or must I go away to escape you"? 6829-68769-0036 5.555 They left him then, for the jailer arrived to unlock the door, and escort them to the office. +6829-68769-0017 3.545 Worse, Tom; worse 'n ever," replied the jailer, gloomily. 6829-68769-0039 4.045 He looked up rather ungraciously, but motioned them to be seated. +6829-68771-0028 3.555 She even seemed mildly amused at the attention she attracted. 6829-68769-0040 4.77 Some girl has been here twice to interview my men and I have refused to admit her. +6829-68769-0012 4.295 Oh, say! that's different," observed Markham, altering his demeanor. 6829-68769-0049 7.4 He detested the grasping disposition that would endeavor to take advantage of his evident desire to help young Gates. +6829-68769-0010 3.14 We wish to talk with him," answered Kenneth. "Talk! 6829-68769-0052 4.6 He might have had that forged check for the face of it, if he'd been sharp. +6829-68769-0051 3.545 There was a grim smile of amusement on his shrewd face. 6829-68769-0053 6.36 And to think we can save all that misery and despair by the payment of a hundred and fifty dollars! +5142-33396-0015 4.31 As our boat flashed down the rollers into the water I made this song and sang it: 5142-36586-0003 5.055 But this subject will be more properly discussed when we treat of the different races of mankind. +3570-5694-0019 3.755 But the general distinction is not on that account to be overlooked. 3570-5696-0002 7.51 Other circumstances permitting, that instinct disposes men to look with favor upon productive efficiency and on whatever is of human use. +3570-5694-0012 3.205 There is a more or less elaborate system of rank and grades. 3570-5696-0004 4.7 The salient features of this development of domestic service have already been indicated. +3570-5694-0012 3.205 There is a more or less elaborate system of rank and grades. 3570-5696-0006 4.16 As used in the speech of everyday life the word carries an undertone of deprecation. +3570-5694-0019 3.755 But the general distinction is not on that account to be overlooked. 3570-5696-0007 9.5 The use of the word "waste" as a technical term, therefore, implies no deprecation of the motives or of the ends sought by the consumer under this canon of conspicuous waste. +3570-5696-0006 4.16 As used in the speech of everyday life the word carries an undertone of deprecation. 3570-5696-0008 7.26 But it is, on other grounds, worth noting that the term "waste" in the language of everyday life implies deprecation of what is characterized as wasteful. +3570-5694-0012 3.205 There is a more or less elaborate system of rank and grades. 3570-5696-0009 8.86 In strict accuracy nothing should be included under the head of conspicuous waste but such expenditure as is incurred on the ground of an invidious pecuniary comparison. +3570-5694-0012 3.205 There is a more or less elaborate system of rank and grades. 3570-5696-0010 7.57 An article may be useful and wasteful both, and its utility to the consumer may be made up of use and waste in the most varying proportions. +2830-3980-0042 3.02 The world brands this a pernicious doctrine. 2830-3980-0005 6.45 Do you suppose that God for the sake of a few Lutheran heretics would disown His entire Church? +2830-3980-0071 3.96 We think that by some little work or merit we can dismiss sin. 2830-3980-0006 6.41 Against these boasting, false apostles, Paul boldly defends his apostolic authority and ministry. +2830-3980-0046 2.84 Was it not enough to say, "from God the Father"? 2830-3980-0008 4.84 Paul takes pride in his ministry, not to his own praise but to the praise of God. +2830-3980-0028 3.54 This should go far in shutting the mouths of the false apostles. 2830-3980-0010 6.525 Either He calls ministers through the agency of men, or He calls them directly as He called the prophets and apostles. +2830-3980-0071 3.96 We think that by some little work or merit we can dismiss sin. 2830-3980-0011 5.525 Paul declares that the false apostles were called or sent neither by men, nor by man. +2830-3980-0028 3.54 This should go far in shutting the mouths of the false apostles. 2830-3980-0013 4.145 He mentions the apostles first because they were appointed directly by God. +2830-3980-0017 3.665 When I was a young man I thought Paul was making too much of his call. 2830-3980-0019 7.015 I knew nothing of the doctrine of faith because we were taught sophistry instead of certainty, and nobody understood spiritual boasting. +2830-3980-0021 2.91 and God the Father, who raised him from the dead. 2830-3980-0023 6.16 These perverters of the righteousness of Christ resist the Father and the Son, and the works of them both. +2830-3980-0020 3.46 This is no sinful pride. It is holy pride. 2830-3980-0025 8.795 By His resurrection Christ won the victory over law, sin, flesh, world, devil, death, hell, and every evil. +2830-3980-0042 3.02 The world brands this a pernicious doctrine. 2830-3980-0029 9.075 Although the brethren with me are not apostles like myself, yet they are all of one mind with me, think, write, and teach as I do". +2830-3980-0000 3.73 In every way they sought to undermine the authority of Saint Paul. 2830-3980-0030 5.25 They do not go where the enemies of the Gospel predominate. They go where the Christians are. +2830-3980-0000 3.73 In every way they sought to undermine the authority of Saint Paul. 2830-3980-0031 8.485 Why do they not invade the Catholic provinces and preach their doctrine to godless princes, bishops, and doctors, as we have done by the help of God? +2830-3980-0042 3.02 The world brands this a pernicious doctrine. 2830-3980-0032 7.22 We look for that reward which "eye hath not seen, nor ear heard, neither hath entered into the heart of man". +2830-3980-0000 3.73 In every way they sought to undermine the authority of Saint Paul. 2830-3980-0036 5.765 Wherever the means of grace are found, there is the Holy Church, even though Antichrist reigns there. +2830-3980-0058 2.69 Mohammed also speaks highly of Christ. 2830-3980-0037 6.42 So much for the title of the epistle. Now follows the greeting of the apostle. VERSE three. +2830-3980-0042 3.02 The world brands this a pernicious doctrine. 2830-3980-0038 5.54 Grace be to you, and peace, from God the Father, and from our Lord Jesus Christ. +2830-3980-0000 3.73 In every way they sought to undermine the authority of Saint Paul. 2830-3980-0039 5.195 The terms of grace and peace are common terms with Paul and are now pretty well understood. +2830-3980-0064 2.88 How may we obtain remission of our sins? 2830-3980-0041 4.89 Grace involves the remission of sins, peace, and a happy conscience. +2830-3980-0024 3.935 In this whole epistle Paul treats of the resurrection of Christ. 2830-3980-0047 7.865 To do so is to lose God altogether because God becomes intolerable when we seek to measure and to comprehend His infinite majesty. +2830-3980-0071 3.96 We think that by some little work or merit we can dismiss sin. 2830-3980-0050 7.475 Did not Christ Himself say: "I am the way, and the truth, and the life: no man cometh unto the Father, but by me"? +2830-3980-0001 3.945 They said to the Galatians: "You have no right to think highly of Paul. 2830-3980-0051 6.44 When you argue about the nature of God apart from the question of justification, you may be as profound as you like. +2830-3980-0046 2.84 Was it not enough to say, "from God the Father"? 2830-3980-0052 4.88 We are to hear Christ, who has been appointed by the Father as our divine Teacher. +2830-3980-0003 2.48 Paul came later and is beneath us. 2830-3980-0053 5.015 At the same time, Paul confirms our creed, "that Christ is very God". +2830-3980-0071 3.96 We think that by some little work or merit we can dismiss sin. 2830-3980-0055 7.335 To bestow peace and grace lies in the province of God, who alone can create these blessings. The angels cannot. +2830-3980-0060 2.675 He never loses sight of the purpose of his epistle. 2830-3980-0056 5.35 Otherwise Paul should have written: "Grace from God the Father, and peace from our Lord Jesus Christ". +2830-3980-0040 2.62 The greeting of the Apostle is refreshing. 2830-3980-0057 8.07 The Arians took Christ for a noble and perfect creature, superior even to the angels, because by Him God created heaven and earth. +2830-3979-0012 3.625 The Word of our God shall stand forever. 2830-3980-0061 7.12 Not gold, or silver, or paschal lambs, or an angel, but Himself. What for? +2830-3980-0034 2.97 These means cannot be contaminated. 2830-3980-0062 5.44 Not for a crown, or a kingdom, or our goodness, but for our sins. +2830-3980-0045 3.51 Men Should Not Speculate About the Nature of God 2830-3980-0063 5.415 Underscore these words, for they are full of comfort for sore consciences. +2830-3980-0042 3.02 The world brands this a pernicious doctrine. 2830-3980-0065 6.515 Paul answers: "The man who is named Jesus Christ and the Son of God gave himself for our sins". +2830-3980-0021 2.91 and God the Father, who raised him from the dead. 2830-3980-0066 6.085 Since Christ was given for our sins it stands to reason that they cannot be put away by our own efforts. +2830-3980-0071 3.96 We think that by some little work or merit we can dismiss sin. 2830-3980-0067 8.13 This sentence also defines our sins as great, so great, in fact, that the whole world could not make amends for a single sin. +2830-3980-0045 3.51 Men Should Not Speculate About the Nature of God 2830-3980-0068 5 The greatness of the ransom, Christ, the Son of God, indicates this. +2830-3980-0040 2.62 The greeting of the Apostle is refreshing. 2830-3980-0069 5.555063 The vicious character of sin is brought out by the words "who gave himself for our sins". +2830-3980-0042 3.02 The world brands this a pernicious doctrine. 2830-3980-0072 4.855 This passage, then, bears out the fact that all men are sold under sin. +2830-3980-0060 2.675 He never loses sight of the purpose of his epistle. 2830-3980-0074 5.7 This attitude is universal and particularly developed in those who consider themselves better than others. +2830-3980-0042 3.02 The world brands this a pernicious doctrine. 2830-3980-0075 5.79 But the real significance and comfort of the words "for our sins" is lost upon them. +2830-3980-0046 2.84 Was it not enough to say, "from God the Father"? 2830-3980-0076 4.81 On the other hand, we are not to regard them as so terrible that we must despair. +5105-28241-0014 2.995 Another circumstance was most remarkable. 5105-28233-0000 4.51 Length of service: Fourteen years, three months, and five days. +5105-28240-0018 2.885 You will take me on board, count, will you not"? 5105-28233-0001 4.49 He seemed born to please without being conscious of the power he possessed. +5105-28240-0018 2.885 You will take me on board, count, will you not"? 5105-28233-0002 8.285 It must be owned, and no one was more ready to confess it than himself, that his literary attainments were by no means of a high order. +5105-28233-0001 4.49 He seemed born to please without being conscious of the power he possessed. 5105-28233-0004 4.735 Once, in action, he was leading a detachment of infantry through an intrenchment. +5105-28241-0003 3.98 Steam up and canvas spread, the schooner started eastwards. 5105-28233-0006 5.505 No cathedral - not even Burgos itself - could vie with the church at Montmartre. +2961-961-0005 3.775 Some poems of Solon were recited by the boys. 2961-961-0000 4.665 Socrates begins the Timaeus with a summary of the Republic. +2961-961-0005 3.775 Some poems of Solon were recited by the boys. 2961-961-0001 9.185 And now he desires to see the ideal State set in motion; he would like to know how she behaved in some great struggle. +2961-961-0005 3.775 Some poems of Solon were recited by the boys. 2961-961-0003 4.73 I will, if Timaeus approves'. 'I approve. +2961-961-0005 3.775 Some poems of Solon were recited by the boys. 2961-961-0006 4.6 And what was the subject of the poem'? said the person who made the remark. +2961-961-0005 3.775 Some poems of Solon were recited by the boys. 2961-961-0007 8.505 The subject was a very noble one; he described the most famous action in which the Athenian people were ever engaged. +2961-961-0005 3.775 Some poems of Solon were recited by the boys. 2961-961-0008 7.155 But the memory of their exploits has passed away owing to the lapse of time and the extinction of the actors. +2961-961-0005 3.775 Some poems of Solon were recited by the boys. 2961-961-0009 5.705 Tell us,' said the other, 'the whole story, and where Solon heard the story. +2961-961-0005 3.775 Some poems of Solon were recited by the boys. 2961-961-0010 7.83 But in Egypt the traditions of our own and other lands are by us registered for ever in our temples. +2961-961-0005 3.775 Some poems of Solon were recited by the boys. 2961-961-0011 7.815 The genealogies which you have recited to us out of your own annals, Solon, are a mere children's story. +2961-961-0005 3.775 Some poems of Solon were recited by the boys. 2961-961-0013 5.12 Solon marvelled, and desired to be informed of the particulars. +2961-961-0005 3.775 Some poems of Solon were recited by the boys. 2961-961-0014 9.565 Nine thousand years have elapsed since she founded yours, and eight thousand since she founded ours, as our annals record. +2961-961-0005 3.775 Some poems of Solon were recited by the boys. 2961-961-0015 6.815 Many laws exist among us which are the counterpart of yours as they were in the olden time. +2961-961-0005 3.775 Some poems of Solon were recited by the boys. 2961-961-0016 7.815 I will briefly describe them to you, and you shall read the account of them at your leisure in the sacred registers. +2961-961-0005 3.775 Some poems of Solon were recited by the boys. 2961-961-0017 9.73 Observe again, what care the law took in the pursuit of wisdom, searching out the deep things of the world, and applying them to the use of man. +2961-961-0005 3.775 Some poems of Solon were recited by the boys. 2961-961-0018 5.29 The most famous of them all was the overthrow of the island of Atlantis. +2961-961-0005 3.775 Some poems of Solon were recited by the boys. 2961-961-0020 6.125 This is the explanation of the shallows which are found in that part of the Atlantic ocean. +2961-961-0005 3.775 Some poems of Solon were recited by the boys. 2961-961-0021 4.94 But I would not speak at the time, because I wanted to refresh my memory. +1995-1837-0015 4.485 The squares of cotton, sharp edged, heavy, were just about to burst to bolls! 1995-1837-0001 8.73 It was the first great sorrow of his life; it was not so much the loss of the cotton itself - but the fantasy, the hopes, the dreams built around it. +1995-1837-0015 4.485 The squares of cotton, sharp edged, heavy, were just about to burst to bolls! 1995-1837-0003 7.36 The revelation of his love lighted and brightened slowly till it flamed like a sunrise over him and left him in burning wonder. +1995-1826-0008 2.895 Some others, too; big cotton county". 1995-1837-0004 6.36 He panted to know if she, too, knew, or knew and cared not, or cared and knew not. +1995-1837-0005 2.635 She was so strange and human a creature. 1995-1837-0007 8.8 Then of a sudden, at midday, the sun shot out, hot and still; no breath of air stirred; the sky was like blue steel; the earth steamed. +1995-1837-0009 3.76 The lagoon had been level with the dykes a week ago; and now? 1995-1837-0012 8.245 He splashed and stamped along, farther and farther onward until he neared the rampart of the clearing, and put foot upon the tree bridge. +1995-1826-0003 3.09 Better go," he had counselled, sententiously. 1995-1837-0016 7.19 For one long moment he paused, stupid, agape with utter amazement, then leaned dizzily against a tree. +1995-1837-0013 3.195 Then he looked down. The lagoon was dry. 1995-1837-0019 5.38 He sat down weak, bewildered, and one thought was uppermost - Zora! +1995-1836-0007 3.435 But you believe in some education"? asked Mary Taylor. 1995-1837-0024 5.385 For a while she lay in her chair, in happy, dreamy pleasure at sun and bird and tree. +1995-1836-0007 3.435 But you believe in some education"? asked Mary Taylor. 1995-1837-0025 9.505062 She rose with a fleeting glance, gathered the shawl round her, then gliding forward, wavering, tremulous, slipped across the road and into the swamp. +1995-1837-0021 3.09 The hope and dream of harvest was upon the land. 1995-1837-0026 8.095 She had been born within its borders; within its borders she had lived and grown, and within its borders she had met her love. +1995-1826-0003 3.09 Better go," he had counselled, sententiously. 1995-1837-0027 6.705 On she hurried until, sweeping down to the lagoon and the island, lo! the cotton lay before her! +1995-1826-0025 3.295 Some time you'll tell me, please, won't you"? 1995-1837-0029 5.58 He darted through the trees and paused, a tall man strongly but slimly made. +5639-40744-0010 4.12 It is the only amends I ask of you for the wrong you have done me". 5639-40744-0002 8.91 Rodolfo and his companions, with their faces muffled in their cloaks, stared rudely and insolently at the mother, the daughter, and the servant maid. +5639-40744-0010 4.12 It is the only amends I ask of you for the wrong you have done me". 5639-40744-0005 5.645 Finally, the one party went off exulting, and the other was left in desolation and woe. +5639-40744-0010 4.12 It is the only amends I ask of you for the wrong you have done me". 5639-40744-0006 8.045 Rodolfo arrived at his own house without any impediment, and Leocadia's parents reached theirs heart broken and despairing. +5639-40744-0010 4.12 It is the only amends I ask of you for the wrong you have done me". 5639-40744-0007 5.825 Meanwhile Rodolfo had Leocadia safe in his custody, and in his own apartment. +5639-40744-0011 2.665 She found the door, but it was locked outside. 5639-40744-0010 4.12 It is the only amends I ask of you for the wrong you have done me". +5639-40744-0011 2.665 She found the door, but it was locked outside. 5639-40744-0012 8.595 She succeeded in opening the window; and the moonlight shone in so brightly, that she could distinguish the colour of some damask hangings in the room. +5639-40744-0010 4.12 It is the only amends I ask of you for the wrong you have done me". 5639-40744-0013 6.865 She saw that the bed was gilded, and so rich, that it seemed that of a prince rather than of a private gentleman. +5639-40744-0010 4.12 It is the only amends I ask of you for the wrong you have done me". 5639-40744-0014 7.72 Among other things on which she cast her eyes was a small crucifix of solid silver, standing on a cabinet near the window. +5639-40744-0011 2.665 She found the door, but it was locked outside. 5639-40744-0016 9.49 On the contrary, he resolved to tell them, that repenting of his violence, and moved by her tears, he had only carried her half way towards his house, and then let her go. +5639-40744-0010 4.12 It is the only amends I ask of you for the wrong you have done me". 5639-40744-0017 5.88 Choking with emotion, Leocadi made a sign to her parents that she wished to be alone with them. +5639-40744-0010 4.12 It is the only amends I ask of you for the wrong you have done me". 5639-40744-0020 9.82 Thus did this humane and right minded father comfort his unhappy daughter; and her mother embracing her again did all she could to soothe her feelings. +5639-40744-0011 2.665 She found the door, but it was locked outside. 5639-40744-0024 8.845 One day, when the boy was sent by his grandfather with a message to a relation, he passed along a street in which there was a great concourse of horsemen. +5639-40744-0011 2.665 She found the door, but it was locked outside. 5639-40744-0025 8.785 The bed she too well remembered was there; and, above all, the cabinet, on which had stood the image she had taken away, was still on the same spot. +5639-40744-0010 4.12 It is the only amends I ask of you for the wrong you have done me". 5639-40744-0029 7.305 This truth which I have learned from her lips is confirmed by his face, in which we have both beheld that of our son". +5639-40744-0010 4.12 It is the only amends I ask of you for the wrong you have done me". 5639-40744-0033 9.15 Her bearing was graceful and animated; she led her son by the hand, and before her walked two maids with wax lights and silver candlesticks. +260-123440-0003 3.585 Oh! won't she be savage if I've kept her waiting"! 260-123440-0010 8.315 How cheerfully he seems to grin, How neatly spread his claws, And welcome little fishes in With gently smiling jaws"! +260-123440-0003 3.585 Oh! won't she be savage if I've kept her waiting"! 260-123440-0011 4.87 No, I've made up my mind about it; if I'm Mabel, I'll stay down here! +260-123288-0019 2.955 At noon the violence of the storm redoubles. 260-123440-0012 5.245 It'll be no use their putting their heads down and saying 'Come up again, dear! +260-123286-0022 3.235 Two hours afterwards a terrible shock awoke me. 260-123440-0015 6.2 I wish I hadn't cried so much"! said Alice, as she swam about, trying to find her way out. +260-123286-0024 3.04 There's a whale, a whale"! cried the Professor. 260-123440-0016 4.895 I shall be punished for it now, I suppose, by being drowned in my own tears! +260-123288-0009 3.435 Those clouds seem as if they were going to crush the sea". 260-123440-0019 6.63 cried Alice again, for this time the Mouse was bristling all over, and she felt certain it must be really offended. +260-123440-0018 3.64 I am very tired of swimming about here, O Mouse"! 260-123440-0020 4.995 We won't talk about her any more if you'd rather not". "We indeed"! +2300-131720-0006 4.12 There seems no good reason for believing that it will change. 2300-131720-0000 5.08 The Paris plant, like that at the Crystal Palace, was a temporary exhibit. +2300-131720-0014 3.75 mister Edison was a leader far ahead of the time. 2300-131720-0005 6.9 Why, if we erect a station at the falls, it is a great economy to get it up to the city. +2300-131720-0041 3.75 We had meters in which there were two bottles of liquid. 2300-131720-0006 4.12 There seems no good reason for believing that it will change. +2300-131720-0014 3.75 mister Edison was a leader far ahead of the time. 2300-131720-0008 9.125 Everything he has done has been aimed at the conservation of energy, the contraction of space, the intensification of culture. +2300-131720-0041 3.75 We had meters in which there were two bottles of liquid. 2300-131720-0009 9.605 For some years it was not found feasible to operate motors on alternating current circuits, and that reason was often urged against it seriously. +2300-131720-0006 4.12 There seems no good reason for believing that it will change. 2300-131720-0015 8.875 He obtained the desired speed and load with a friction brake; also regulator of speed; but waited for an indicator to verify it. +2300-131720-0041 3.75 We had meters in which there were two bottles of liquid. 2300-131720-0024 4.77 But the plant ran, and it was the first three wire station in this country". +2300-131720-0014 3.75 mister Edison was a leader far ahead of the time. 2300-131720-0027 8.62 Edison held that the electricity sold must be measured just like gas or water, and he proceeded to develop a meter. +2300-131720-0014 3.75 mister Edison was a leader far ahead of the time. 2300-131720-0029 6.425 Hence the Edison electrolytic meter is no longer used, despite its excellent qualities. +2300-131720-0006 4.12 There seems no good reason for believing that it will change. 2300-131720-0030 9.98 The principle employed in the Edison electrolytic meter is that which exemplifies the power of electricity to decompose a chemical substance. +2300-131720-0041 3.75 We had meters in which there were two bottles of liquid. 2300-131720-0034 8.605 the others having been in operation too short a time to show definite results, although they also went quickly to a dividend basis. +2300-131720-0014 3.75 mister Edison was a leader far ahead of the time. 2300-131720-0037 7.965 He weighed and reweighed the meter plates, and pursued every line of investigation imaginable, but all in vain. +2300-131720-0041 3.75 We had meters in which there were two bottles of liquid. 2300-131720-0038 5.61 He felt he was up against it, and that perhaps another kind of a job would suit him better. +2300-131720-0041 3.75 We had meters in which there were two bottles of liquid. 2300-131720-0040 5.455 We were more interested in the technical condition of the station than in the commercial part. +908-157963-0002 2.755 why fades the lotus of the water? 908-31957-0002 4.79 I did not wrong myself so, but I placed A wrong on thee. +908-157963-0018 4.255 And fearest thou because I vanish and am seen no more. 908-31957-0003 6.565 When called before, I told how hastily I dropped my flowers or brake off from a game. +908-157963-0001 2.885 O life of this our spring! 908-31957-0005 4.49 Alas, I have grieved so I am hard to love. +908-31957-0018 3.915 But thou art not such A lover, my Beloved! 908-31957-0006 5.89 Open thy heart wide, And fold within, the wet wings of thy dove. +908-157963-0002 2.755 why fades the lotus of the water? 908-31957-0007 5.8 Could it mean To last, a love set pendulous between Sorrow and sorrow? +908-157963-0029 3.63 Why a Tongue impressed with honey from every wind? 908-31957-0009 7.705 And, though I have grown serene And strong since then, I think that God has willed A still renewable fear... +908-31957-0005 4.49 Alas, I have grieved so I am hard to love. 908-31957-0012 7.615 if he, to keep one oath, Must lose one joy, by his life's star foretold. +908-157963-0002 2.755 why fades the lotus of the water? 908-31957-0013 6.18 Slow to world greetings, quick with its "O, list," When the angels speak. +908-157963-0024 3.44 image of weakness, art thou but a Worm? 908-31957-0014 7.56 A ring of amethyst I could not wear here, plainer to my sight, Than that first kiss. +908-31957-0005 4.49 Alas, I have grieved so I am hard to love. 908-31957-0016 6.48 Dearest, teach me so To pour out gratitude, as thou dost, good! +908-31957-0018 3.915 But thou art not such A lover, my Beloved! 908-31957-0017 7.795 Mussulmans and Giaours Throw kerchiefs at a smile, and have no ruth For any weeping. +908-157963-0002 2.755 why fades the lotus of the water? 908-31957-0019 9.54 thou canst wait Through sorrow and sickness, to bring souls to touch, And think it soon when others cry "Too late". +908-157963-0013 4.315 And why it scatters its bright beauty thro the humid air. 908-31957-0020 5.895 I thank all who have loved me in their hearts, With thanks and love from mine. +908-31957-0018 3.915 But thou art not such A lover, my Beloved! 908-31957-0023 8.515 I love thee freely, as men strive for Right; I love thee purely, as they turn from Praise. +908-157963-0002 2.755 why fades the lotus of the water? 908-31957-0024 7.54 I love thee with the passion put to use In my old griefs, and with my childhood's faith. +4992-41797-0016 3.3 They couldn't run nor move; they're just pasteboard". 4992-41806-0001 8.31 To night there was no need of extra heat, and there were great ceremonies to be observed in lighting the fires on the hearthstones. +4992-41797-0016 3.3 They couldn't run nor move; they're just pasteboard". 4992-41806-0003 9.24 Kathleen waved the torch to and fro as she recited some beautiful lines written for some such purpose as that which called them together to night. +4992-41797-0016 3.3 They couldn't run nor move; they're just pasteboard". 4992-41806-0009 4.355 exclaimed Bill Harmon to his wife as they went through the lighted hall. +4992-23283-0007 4.045 To ask any more questions of you, I believe, would be unfair. 4992-41806-0011 7.84 Mother Carey poured coffee, Nancy chocolate, and the others helped serve the sandwiches and cake, doughnuts and tarts. +4992-23283-0016 4.495 Again he searched his own thoughts; nor ineffectually as before. 4992-41806-0012 6.73 At that moment the gentleman entered, bearing a huge object concealed by a piece of green felt. +4992-41797-0016 3.3 They couldn't run nor move; they're just pasteboard". 4992-41806-0013 6.02 Approaching the dining table, he carefully placed the article in the centre and removed the cloth. +7021-85628-0004 2.805 Yes, why not"? thought Anders. 7021-85628-0002 6.455 He was such a big boy that he wore high boots and carried a jack knife. +7021-85628-0006 3.58 I am going to the court ball," answered Anders. 7021-85628-0005 5.015 Seeing that I am so fine, I may as well go and visit the King". +7021-85628-0025 2.775 But his mother hugged him close. 7021-85628-0008 7.125 For, like as not, they must have thought him a prince when they saw his fine cap. +7021-79759-0001 2.48 That is comparatively nothing. 7021-85628-0009 8.54 At the farther end of the largest hall a table was set with golden cups and golden plates in long rows. +7021-85628-0019 3.255 With one jump Anders got out of his chair. 7021-85628-0010 8.015 On huge silver platters were pyramids of tarts and cakes, and red wine sparkled in glittering decanters. +7021-79740-0012 3.26 said she, pointing to the playthings; "see! 7021-85628-0011 8.995 The Princess sat down under a blue canopy with bouquets of roses; and she let Anders sit in a golden chair by her side. +7021-79740-0009 3.635 They were now playing with their dolls in the parlor. 7021-85628-0012 5.33 But you must not eat with your cap on your head," she said, and was going to take it off. +7021-85628-0026 2.74 No, my little son," she said. 7021-85628-0016 4.28 That is a very fine cap you have," he said. +7021-79740-0012 3.26 said she, pointing to the playthings; "see! 7021-85628-0018 8.22 And it is made of mother's best yarn, and she knitted it herself, and everybody wants to get it away from me". +7021-79740-0012 3.26 said she, pointing to the playthings; "see! 7021-85628-0020 6.45 He darted like an arrow through all the halls, down all the stairs, and across the yard. +7021-79740-0009 3.635 They were now playing with their dolls in the parlor. 7021-85628-0021 5.365 He still held on to it with both hands as he rushed into his mother's cottage. +7021-79740-0009 3.635 They were now playing with their dolls in the parlor. 7021-85628-0022 5.145 And all his brothers and sisters stood round and listened with their mouths open. +7021-79740-0009 3.635 They were now playing with their dolls in the parlor. 7021-85628-0023 9.03 But when his big brother heard that he had refused to give his cap for a King's golden crown, he said that Anders was a stupid. +7021-85628-0019 3.255 With one jump Anders got out of his chair. 7021-85628-0027 8.5 If you dressed in silk and gold from top to toe, you could not look any nicer than in your little red cap". +1284-1181-0007 4.04 She poured into the dish a quantity from each of these bottles. 1284-134647-0000 8.53 The grateful applause of the clergy has consecrated the memory of a prince who indulged their passions and promoted their interest. +4970-29095-0011 3.355 Does thee think thee could stand it six months? 4970-29095-0002 5.48 Well, mother," said the young student, looking up, with a shade of impatience. +4970-29095-0006 4.47 Is thy father willing thee should go away to a school of the world's people"? 4970-29095-0004 9.61 I heard father tell cousin Abner that he was whipped so often for whistling when he was a boy that he was determined to have what compensation he could get now". +4970-29095-0014 3.26 Where thee and thy family are known"? 4970-29095-0005 4.65 Thy ways greatly try me, Ruth, and all thy relations. +4970-29093-0015 3.325 You can begin by carrying a rod, and putting down the figures. 4970-29095-0006 4.47 Is thy father willing thee should go away to a school of the world's people"? +4970-29095-0014 3.26 Where thee and thy family are known"? 4970-29095-0009 5.6 Margaret Bolton almost lost for a moment her habitual placidity. +4970-29095-0000 2.865 She was tired of other things. 4970-29095-0012 4.68 And, besides, suppose thee does learn medicine"? +4970-29095-0014 3.26 Where thee and thy family are known"? 4970-29095-0016 6.945 Ruth sat quite still for a time, with face intent and flushed. It was out now. +4970-29095-0011 3.355 Does thee think thee could stand it six months? 4970-29095-0022 4.765 Is thee going to the Yearly Meeting, Ruth"? asked one of the girls. +4970-29093-0000 3.03 You'll never dig it out of the Astor Library". 4970-29095-0024 6.04 It has occupied mother a long time, to find at the shops the exact shade for her new bonnet. +4970-29093-0017 2.865 I've been ready to go anywhere for six months. 4970-29095-0027 9.795 It's such a crush at the Yearly Meeting at Arch Street, and then there's the row of sleek looking young men who line the curbstone and stare at us as we come out. +4970-29093-0008 3.58 He wanted to begin at the top of the ladder. 4970-29095-0030 4.67 Father, thee's unjust to Philip. He's going into business". +4970-29095-0017 3.93 The sight seers returned in high spirits from the city. 4970-29095-0032 6.61 But Philip is honest, and he has talent enough, if he will stop scribbling, to make his way. +4970-29093-0008 3.58 He wanted to begin at the top of the ladder. 4970-29095-0034 5.81 Why should I rust, and be stupid, and sit in inaction because I am a girl? +4970-29095-0011 3.355 Does thee think thee could stand it six months? 4970-29095-0035 4.75 And if I had a fortune, would thee want me to lead a useless life"? +4970-29093-0017 2.865 I've been ready to go anywhere for six months. 4970-29095-0036 5.25 Has thee consulted thy mother about a career, I suppose it is a career thee wants"? +4970-29093-0000 3.03 You'll never dig it out of the Astor Library". 4970-29095-0037 6.885 But that wise and placid woman understood the sweet rebel a great deal better than Ruth understood herself. +4970-29093-0000 3.03 You'll never dig it out of the Astor Library". 4970-29095-0038 8.74 Ruth was glad to hear that Philip had made a push into the world, and she was sure that his talent and courage would make a way for him. +121-127105-0032 3.17 Yes, but that's just the beauty of her passion". 121-127105-0000 9.875 It was this observation that drew from Douglas not immediately, but later in the evening a reply that had the interesting consequence to which I call attention. +121-127105-0018 2.77 cried the ladies whose departure had been fixed. 121-127105-0001 5.025 Someone else told a story not particularly effective, which I saw he was not following. +121-127105-0032 3.17 Yes, but that's just the beauty of her passion". 121-127105-0002 7.495 cried one of the women. He took no notice of her; he looked at me, but as if, instead of me, he saw what he spoke of. +121-127105-0036 4.15 But was that all her reward"? one of the ladies asked. 121-127105-0003 7.725 There was a unanimous groan at this, and much reproach; after which, in his preoccupied way, he explained. +121-127105-0032 3.17 Yes, but that's just the beauty of her passion". 121-127105-0005 5.82 I could write to my man and enclose the key; he could send down the packet as he finds it". +121-127105-0018 2.77 cried the ladies whose departure had been fixed. 121-127105-0006 4.725 The others resented postponement, but it was just his scruples that charmed me. +121-127105-0036 4.15 But was that all her reward"? one of the ladies asked. 121-127105-0007 5.79 To this his answer was prompt. "Oh, thank God, no"! "And is the record yours? +121-127105-0010 2.85 She sent me the pages in question before she died". 121-127105-0011 5.78 She was the most agreeable woman I've ever known in her position; she would have been worthy of any whatever. +121-127105-0010 2.85 She sent me the pages in question before she died". 121-127105-0012 4.83 It wasn't simply that she said so, but that I knew she hadn't. I was sure; I could see. +121-127105-0010 2.85 She sent me the pages in question before she died". 121-127105-0013 5.895 You'll easily judge why when you hear". "Because the thing had been such a scare"? He continued to fix me. +121-127105-0036 4.15 But was that all her reward"? one of the ladies asked. 121-127105-0022 5.075 Well, if I don't know who she was in love with, I know who he was". +121-127105-0018 2.77 cried the ladies whose departure had been fixed. 121-127105-0026 7.53 The first of these touches conveyed that the written statement took up the tale at a point after it had, in a manner, begun. +121-127105-0018 2.77 cried the ladies whose departure had been fixed. 121-127105-0028 6.75 The awkward thing was that they had practically no other relations and that his own affairs took up all his time. +121-127105-0015 2.96 He quitted the fire and dropped back into his chair. 121-127105-0029 7.31 There were plenty of people to help, but of course the young lady who should go down as governess would be in supreme authority. +121-127105-0036 4.15 But was that all her reward"? one of the ladies asked. 121-127105-0034 7.41 It sounded dull it sounded strange; and all the more so because of his main condition". "Which was-"? +121-127105-0008 2.76 He hung fire again. "A woman's. 121-127105-0036 4.15 But was that all her reward"? one of the ladies asked. +260-123288-0012 3.545 That will be safest". "No, no! Never"! 260-123286-0000 7.04 Saturday, august fifteenth. - The sea unbroken all round. No land in sight. +260-123286-0012 2.43 But there seemed no reason to fear. 260-123286-0002 9.985 All my danger and sufferings were needed to strike a spark of human feeling out of him; but now that I am well his nature has resumed its sway. +260-123440-0005 3.105 And yesterday things went on just as usual. 260-123286-0003 7.37 You seem anxious, my uncle," I said, seeing him continually with his glass to his eye. "Anxious! +260-123286-0024 3.04 There's a whale, a whale"! cried the Professor. 260-123286-0005 4.81 I am not complaining that the rate is slow, but that the sea is so wide". +260-123288-0019 2.955 At noon the violence of the storm redoubles. 260-123286-0006 7.405 We are losing time, and the fact is, I have not come all this way to take a little sail upon a pond on a raft". +260-123288-0019 2.955 At noon the violence of the storm redoubles. 260-123286-0007 4.55 He called this sea a pond, and our long voyage, taking a little sail! +260-123286-0022 3.235 Two hours afterwards a terrible shock awoke me. 260-123286-0009 5.795 I take this as my answer, and I leave the Professor to bite his lips with impatience. +260-123286-0001 3.07 The horizon seems extremely distant. 260-123286-0011 4.255 Nothing new. Weather unchanged. The wind freshens. +260-123286-0024 3.04 There's a whale, a whale"! cried the Professor. 260-123286-0013 4.73 The shadow of the raft was clearly outlined upon the surface of the waves. +260-123440-0018 3.64 I am very tired of swimming about here, O Mouse"! 260-123286-0015 5.21 It must be as wide as the Mediterranean or the Atlantic - and why not? +260-123288-0019 2.955 At noon the violence of the storm redoubles. 260-123286-0016 7 These thoughts agitated me all day, and my imagination scarcely calmed down after several hours' sleep. +260-123440-0006 2.715 I wonder if I've been changed in the night? 260-123286-0018 5.67 I saw at the Hamburg museum the skeleton of one of these creatures thirty feet in length. +260-123288-0009 3.435 Those clouds seem as if they were going to crush the sea". 260-123286-0023 5.875 The raft was heaved up on a watery mountain and pitched down again, at a distance of twenty fathoms. +260-123286-0024 3.04 There's a whale, a whale"! cried the Professor. 260-123286-0025 9.205 Flight was out of the question now. The reptiles rose; they wheeled around our little raft with a rapidity greater than that of express trains. +260-123288-0020 2.9 Each of us is lashed to some part of the raft. 260-123286-0026 6.94 Two monsters only were creating all this commotion; and before my eyes are two reptiles of the primitive world. +260-123286-0022 3.235 Two hours afterwards a terrible shock awoke me. 260-123286-0027 7.17 I can distinguish the eye of the ichthyosaurus glowing like a red hot coal, and as large as a man's head. +260-123286-0024 3.04 There's a whale, a whale"! cried the Professor. 260-123286-0029 4.545 Those huge creatures attacked each other with the greatest animosity. +260-123440-0005 3.105 And yesterday things went on just as usual. 260-123286-0030 7.53 Suddenly the ichthyosaurus and the plesiosaurus disappear below, leaving a whirlpool eddying in the water. +260-123288-0022 3.705 They seem to be 'We are lost'; but I am not sure. 260-123286-0031 5.06 As for the ichthyosaurus - has he returned to his submarine cavern? +3575-170457-0031 4 On august twenty seventh, eighteen thirty seven, she writes: 3575-170457-0000 8.23 And often has my mother said, While on her lap I laid my head, She feared for time I was not made, But for Eternity. +3575-170457-0032 3.03 Come, come. I am getting really tired of your absence. 3575-170457-0003 7.595 Surely, it must be because we are in danger of loving each other too well - of losing sight of the Creator in idolatry of the creature. +3575-170457-0049 2.715 This decision was communicated to the girls. 3575-170457-0005 7.34 She, a Tory and clergyman's daughter, was always in a minority of one in our house of violent Dissent and Radicalism. +3575-170457-0052 3 She had another weight on her mind this Christmas. 3575-170457-0006 8.3 Her feeble health gave her her yielding manner, for she could never oppose any one without gathering up all her strength for the struggle. +3575-170457-0031 4 On august twenty seventh, eighteen thirty seven, she writes: 3575-170457-0007 7.775 He spoke French perfectly, I have been told, when need was; but delighted usually in talking the broadest Yorkshire. +3575-170457-0031 4 On august twenty seventh, eighteen thirty seven, she writes: 3575-170457-0010 4.79 I am not depreciating it when I say that in these times it is not rare. +3575-170457-0049 2.715 This decision was communicated to the girls. 3575-170457-0011 7.015 But it is not with a view to distinction that you should cultivate this talent, if you consult your own happiness. +3575-170457-0049 2.715 This decision was communicated to the girls. 3575-170457-0012 5.850062 You will say that a woman has no need of such a caution; there can be no peril in it for her. +3575-170457-0031 4 On august twenty seventh, eighteen thirty seven, she writes: 3575-170457-0013 9.175 The more she is engaged in her proper duties, the less leisure will she have for it, even as an accomplishment and a recreation. +3575-170457-0031 4 On august twenty seventh, eighteen thirty seven, she writes: 3575-170457-0014 6.68 To those duties you have not yet been called, and when you are you will be less eager for celebrity. +3575-170457-0004 3.105 We used to dispute about politics and religion. 3575-170457-0019 6.155 I had not ventured to hope for such a reply; so considerate in its tone, so noble in its spirit. +3575-170457-0056 3.370062 I doubt whether Branwell was maintaining himself at this time. 3575-170457-0020 8.645 I know the first letter I wrote to you was all senseless trash from beginning to end; but I am not altogether the idle dreaming being it would seem to denote. +3575-170457-0032 3.03 Come, come. I am getting really tired of your absence. 3575-170457-0021 4.18 I thought it therefore my duty, when I left school, to become a governess. +3575-170457-0004 3.105 We used to dispute about politics and religion. 3575-170457-0022 5.825 In the evenings, I confess, I do think, but I never trouble any one else with my thoughts. +3575-170457-0049 2.715 This decision was communicated to the girls. 3575-170457-0023 9.095 I carefully avoid any appearance of preoccupation and eccentricity, which might lead those I live amongst to suspect the nature of my pursuits. +3575-170457-0034 3.495 in this monotonous life of mine, that was a pleasant event. 3575-170457-0025 9.205 Again I thank you. This incident, I suppose, will be renewed no more; if I live to be an old woman, I shall remember it thirty years hence as a bright dream. +3575-170457-0031 4 On august twenty seventh, eighteen thirty seven, she writes: 3575-170457-0027 4.58 I cannot deny myself the gratification of inserting Southey's reply: +3575-170457-0049 2.715 This decision was communicated to the girls. 3575-170457-0029 6.055 Your letter has given me great pleasure, and I should not forgive myself if I did not tell you so. +3575-170457-0049 2.715 This decision was communicated to the girls. 3575-170457-0030 8.945063 Of this second letter, also, she spoke, and told me that it contained an invitation for her to go and see the poet if ever she visited the Lakes. +3575-170457-0021 4.18 I thought it therefore my duty, when I left school, to become a governess. 3575-170457-0033 8.5 Saturday after Saturday comes round, and I can have no hope of hearing your knock at the door, and then being told that 'Miss E. is come'. Oh, dear! +3575-170457-0049 2.715 This decision was communicated to the girls. 3575-170457-0035 9.37 I wish it would recur again; but it will take two or three interviews before the stiffness - the estrangement of this long separation - will wear away". +3575-170457-0034 3.495 in this monotonous life of mine, that was a pleasant event. 3575-170457-0040 6.905 Indeed, there were only one or two strangers who could be admitted among the sisters without producing the same result. +3575-170457-0049 2.715 This decision was communicated to the girls. 3575-170457-0044 9.72 After this disappointment, I never dare reckon with certainty on the enjoyment of a pleasure again; it seems as if some fatality stood between you and me. +3575-170457-0001 2.99 Why are we to be denied each other's society? 3575-170457-0045 6.52 I am not good enough for you, and you must be kept from the contamination of too intimate society. +3575-170457-0049 2.715 This decision was communicated to the girls. 3575-170457-0047 6.525 Tabby had lived with them for ten or twelve years, and was, as Charlotte expressed it, "one of the family". +3575-170457-0052 3 She had another weight on her mind this Christmas. 3575-170457-0048 5.555 He refused at first to listen to the careful advice; it was repugnant to his liberal nature. +3575-170457-0049 2.715 This decision was communicated to the girls. 3575-170457-0050 6.405 Tabby had tended them in their childhood; they, and none other, should tend her in her infirmity and age. +3575-170457-0056 3.370062 I doubt whether Branwell was maintaining himself at this time. 3575-170457-0051 4.915 At tea time, they were sad and silent, and the meal went away untouched by any of the three. +3575-170457-0031 4 On august twenty seventh, eighteen thirty seven, she writes: 3575-170457-0054 8.005 Stung by anxiety for this little sister, she upbraided Miss W -- for her fancied indifference to Anne's state of health. +4970-29093-0008 3.58 He wanted to begin at the top of the ladder. 4970-29093-0007 6.995 It is such a noble ambition, that it is a pity it has usually such a shallow foundation. +4970-29095-0011 3.355 Does thee think thee could stand it six months? 4970-29093-0009 9.12 Philip therefore read diligently in the Astor library, planned literary works that should compel attention, and nursed his genius. +4970-29093-0017 2.865 I've been ready to go anywhere for six months. 4970-29093-0012 8.71 But Philip did afford it, and he wrote, thanking his friends, and declining because he said the political scheme would fail, and ought to fail. +4970-29093-0017 2.865 I've been ready to go anywhere for six months. 4970-29093-0013 8.01 And he went back to his books and to his waiting for an opening large enough for his dignified entrance into the literary world. +4970-29095-0008 3.04 Mother, I'm going to study medicine"? 4970-29093-0014 4.275 Well, I'm going as an engineer. You can go as one". +4970-29093-0017 2.865 I've been ready to go anywhere for six months. 4970-29093-0018 9.715 The two young men who were by this time full of the adventure, went down to the Wall street office of Henry's uncle and had a talk with that wily operator. +4970-29093-0015 3.325 You can begin by carrying a rod, and putting down the figures. 4970-29093-0019 7.47 The night was spent in packing up and writing letters, for Philip would not take such an important step without informing his friends. +4970-29093-0004 3.75 He was unable to decide exactly what it should be. 4970-29093-0020 5.58 Why, it's in Missouri somewhere, on the frontier I think. We'll get a map". +4970-29093-0017 2.865 I've been ready to go anywhere for six months. 4970-29093-0022 6.22 He knew his uncle would be glad to hear that he had at last turned his thoughts to a practical matter. +4970-29095-0011 3.355 Does thee think thee could stand it six months? 4970-29093-0023 8.07 He well knew the perils of the frontier, the savage state of society, the lurking Indians and the dangers of fever. +1284-1181-0019 3.2 I now use them as ornamental statuary in my garden. 1284-1180-0000 8.12 He wore blue silk stockings, blue knee pants with gold buckles, a blue ruffled waist and a jacket of bright blue braided with gold. +1284-1181-0007 4.04 She poured into the dish a quantity from each of these bottles. 1284-1180-0001 7.755 His hat had a peaked crown and a flat brim, and around the brim was a row of tiny golden bells that tinkled when he moved. +1284-1181-0021 2.7 asked the voice, in scornful accents. 1284-1180-0002 7.68 Instead of shoes, the old man wore boots with turnover tops and his blue coat had wide cuffs of gold braid. +1284-1180-0004 4.285 When they were outside, Unc simply latched the door and started up the path. 1284-1180-0003 4.835 For a long time he had wished to explore the beautiful Land of Oz in which they lived. +1284-1180-0014 3.665 Ojo had never eaten such a fine meal in all his life. 1284-1180-0004 4.285 When they were outside, Unc simply latched the door and started up the path. +1284-1181-0002 3.835 The head of the Patchwork Girl was the most curious part of her. 1284-1180-0005 6.55 No one would disturb their little house, even if anyone came so far into the thick forest while they were gone. +1284-1180-0004 4.285 When they were outside, Unc simply latched the door and started up the path. 1284-1180-0006 6.865 At the foot of the mountain that separated the Country of the Munchkins from the Country of the Gillikins, the path divided. +1284-1180-0004 4.285 When they were outside, Unc simply latched the door and started up the path. 1284-1180-0007 6.265 He knew it would take them to the house of the Crooked Magician, whom he had never seen but who was their nearest neighbor. +1284-1180-0014 3.665 Ojo had never eaten such a fine meal in all his life. 1284-1180-0009 6.285 Then they started on again and two hours later came in sight of the house of doctor Pipt. +1284-1181-0002 3.835 The head of the Patchwork Girl was the most curious part of her. 1284-1180-0010 8.635 Unc knocked at the door of the house and a chubby, pleasant faced woman, dressed all in blue, opened it and greeted the visitors with a smile. +1284-1180-0014 3.665 Ojo had never eaten such a fine meal in all his life. 1284-1180-0011 4.275 I am, my dear, and all strangers are welcome to my home". +1284-1180-0011 4.275 I am, my dear, and all strangers are welcome to my home". 1284-1180-0012 4.88 We have come from a far lonelier place than this". "A lonelier place! +1284-1180-0022 2.885 I'm afraid I don't know much about the Land of Oz. 1284-1180-0015 5.835 We are traveling," replied Ojo, "and we stopped at your house just to rest and refresh ourselves. +1284-1180-0004 4.285 When they were outside, Unc simply latched the door and started up the path. 1284-1180-0020 5.87 The first lot we tested on our Glass Cat, which not only began to live but has lived ever since. +1284-1181-0002 3.835 The head of the Patchwork Girl was the most curious part of her. 1284-1180-0021 9.84 I think the next Glass Cat the Magician makes will have neither brains nor heart, for then it will not object to catching mice and may prove of some use to us". +1284-1180-0022 2.885 I'm afraid I don't know much about the Land of Oz. 1284-1180-0023 5.61 You see, I've lived all my life with Unc Nunkie, the Silent One, and there was no one to tell me anything". +1284-1181-0007 4.04 She poured into the dish a quantity from each of these bottles. 1284-1180-0024 5.26 That is one reason you are Ojo the Unlucky," said the woman, in a sympathetic tone. +1284-1181-0007 4.04 She poured into the dish a quantity from each of these bottles. 1284-1180-0025 8.705 I think I must show you my Patchwork Girl," said Margolotte, laughing at the boy's astonishment, "for she is rather difficult to explain. +1284-1180-0004 4.285 When they were outside, Unc simply latched the door and started up the path. 1284-1180-0026 8.29 But first I will tell you that for many years I have longed for a servant to help me with the housework and to cook the meals and wash the dishes. +1284-1181-0007 4.04 She poured into the dish a quantity from each of these bottles. 1284-1180-0028 6.045 A bed quilt made of patches of different kinds and colors of cloth, all neatly sewed together. +1284-1181-0002 3.835 The head of the Patchwork Girl was the most curious part of her. 1284-1180-0029 5.335 Sometimes it is called a 'crazy quilt,' because the patches and colors are so mixed up. +1284-1180-0004 4.285 When they were outside, Unc simply latched the door and started up the path. 1284-1180-0031 4.825 At the Emerald City, where our Princess Ozma lives, green is the popular color. +1284-1181-0002 3.835 The head of the Patchwork Girl was the most curious part of her. 1284-1180-0032 5.78 I will show you what a good job I did," and she went to a tall cupboard and threw open the doors. +3570-5694-0022 4.295 The livery becomes obnoxious to nearly all who are required to wear it. 3570-5694-0001 5.675 The utility of consumption as an evidence of wealth is to be classed as a derivative growth. +3570-5694-0022 4.295 The livery becomes obnoxious to nearly all who are required to wear it. 3570-5694-0004 5.33 In the nature of things, luxuries and the comforts of life belong to the leisure class. +3570-5694-0022 4.295 The livery becomes obnoxious to nearly all who are required to wear it. 3570-5694-0005 8.405 Under the tabu, certain victuals, and more particularly certain beverages, are strictly reserved for the use of the superior class. +3570-5694-0022 4.295 The livery becomes obnoxious to nearly all who are required to wear it. 3570-5694-0008 9.495 The consumption of luxuries, in the true sense, is a consumption directed to the comfort of the consumer himself, and is, therefore, a mark of the master. +3570-5694-0012 3.205 There is a more or less elaborate system of rank and grades. 3570-5694-0013 5.61 This differentiation is furthered by the inheritance of wealth and the consequent inheritance of gentility. +3570-5694-0022 4.295 The livery becomes obnoxious to nearly all who are required to wear it. 3570-5694-0015 8.435 So many of them, however, as make up the retainer and hangers on of the patron may be classed as vicarious consumer without qualification. +3570-5694-0019 3.755 But the general distinction is not on that account to be overlooked. 3570-5694-0017 8.335 The wearing of uniforms or liveries implies a considerable degree of dependence, and may even be said to be a mark of servitude, real or ostensible. +3570-5694-0022 4.295 The livery becomes obnoxious to nearly all who are required to wear it. 3570-5694-0018 7.815 The wearers of uniforms and liveries may be roughly divided into two classes the free and the servile, or the noble and the ignoble. +3570-5694-0019 3.755 But the general distinction is not on that account to be overlooked. 3570-5694-0022 4.295 The livery becomes obnoxious to nearly all who are required to wear it. +8463-287645-0010 4.325 He worked me very hard; he wanted to be beating me all the time". 8463-294828-0001 9.19 THREE SECONDS before the arrival of JB Hobson's letter, I no more dreamed of chasing the unicorn than of trying for the Northwest Passage. +8463-287645-0014 3.02 of starting. I didn't know the way to come. 8463-294828-0002 6.19 Even so, I had just returned from an arduous journey, exhausted and badly needing a rest. +8463-294828-0021 2.735 A route slightly less direct, that's all. 8463-294828-0003 9.34 I wanted nothing more than to see my country again, my friends, my modest quarters by the Botanical Gardens, my dearly beloved collections! +8463-294828-0026 2.745 We have a commander who's game for anything"! 8463-294828-0006 7.32 From rubbing shoulders with scientists in our little universe by the Botanical Gardens, the boy had come to know a thing or two. +8463-294828-0026 2.745 We have a commander who's game for anything"! 8463-294828-0009 4.17 Not once did he comment on the length or the hardships of a journey. +8463-287645-0009 3.71 I never knew of but one man who could ever please him. 8463-294828-0010 8.34 Never did he object to buckling up his suitcase for any country whatever, China or the Congo, no matter how far off it was. +8463-294828-0011 3.91 He went here, there, and everywhere in perfect contentment. 8463-294828-0012 4.905 Please forgive me for this underhanded way of admitting I had turned forty. +8463-287645-0008 3.325 As usual nothing was done in the way of punishment". 8463-294828-0013 7.2 He was a fanatic on formality, and he only addressed me in the third person to the point where it got tiresome. +8463-287645-0009 3.71 I never knew of but one man who could ever please him. 8463-294828-0014 5.725 There was good reason to stop and think, even for the world's most emotionless man. +8463-294828-0005 2.44 Conseil was my manservant. 8463-294828-0015 4.88 Conseil"! I called a third time. Conseil appeared. +8463-287645-0008 3.325 As usual nothing was done in the way of punishment". 8463-294828-0017 9.3 Pack as much into my trunk as you can, my traveling kit, my suits, shirts, and socks, don't bother counting, just squeeze it all in and hurry"! +8463-294825-0008 3.98 But much of the novel's brooding power comes from Captain Nemo. 8463-294828-0019 4.53 Anyhow, we'll leave instructions to ship the whole menagerie to France". +8463-287645-0001 3.545 It is hardly necessary to say more of them here. 8463-294828-0020 5.915 Yes, we are... certainly...," I replied evasively, "but after we make a detour". +8463-287645-0008 3.325 As usual nothing was done in the way of punishment". 8463-294828-0023 4.745 You see, my friend, it's an issue of the monster, the notorious narwhale. +8463-294828-0026 2.745 We have a commander who's game for anything"! 8463-294828-0027 5.98 I left instructions for shipping my containers of stuffed animals and dried plants to Paris, France. +8463-294828-0034 3.505 We'll be quite comfortable here," I told Conseil. 8463-294828-0028 7.915 I opened a line of credit sufficient to cover the babirusa and, Conseil at my heels, I jumped into a carriage. +8463-294828-0011 3.91 He went here, there, and everywhere in perfect contentment. 8463-294828-0029 5.285 Our baggage was immediately carried to the deck of the frigate. I rushed aboard. +8463-294828-0026 2.745 We have a commander who's game for anything"! 8463-294828-0031 7.765 One of the sailors led me to the afterdeck, where I stood in the presence of a smart looking officer who extended his hand to me. +8463-287645-0008 3.325 As usual nothing was done in the way of punishment". 8463-294828-0032 4.395 In person. Welcome aboard, professor. Your cabin is waiting for you". +8463-294825-0008 3.98 But much of the novel's brooding power comes from Captain Nemo. 8463-294828-0033 6.365 I was well satisfied with my cabin, which was located in the stern and opened into the officers' mess. +8463-294828-0009 4.17 Not once did he comment on the length or the hardships of a journey. 8463-294828-0036 6.985 The wharves of Brooklyn, and every part of New York bordering the East River, were crowded with curiosity seekers. +7127-75947-0008 4.155 The arrow pierced his heart and wounded him mortally. 7127-75947-0001 6.64 Upon this Madame deigned to turn her eyes languishingly towards the comte, observing. +7127-75947-0002 3.235 Do you think so"? she replied with indifference. 7127-75947-0003 5.98 Yes; the character which your royal highness assumed is in perfect harmony with your own". +7127-75946-0025 3.96 The ballet began; the effect was more than beautiful. 7127-75947-0007 5.46 She then rose, humming the air to which she was presently going to dance. +7127-75946-0005 2.67 What do you mean"? inquired Louis, 7127-75947-0008 4.155 The arrow pierced his heart and wounded him mortally. +7127-75947-0002 3.235 Do you think so"? she replied with indifference. 7127-75947-0010 8.865 When she perceived the young man, she rose, like a woman surprised in the midst of ideas she was desirous of concealing from herself. +7127-75946-0005 2.67 What do you mean"? inquired Louis, 7127-75947-0013 5.045 I remember now, and I congratulate myself. Do you love any one"? +7127-75947-0018 4.04 I have been here this quarter of an hour," replied La Valliere. 7127-75947-0015 6.26 There cannot be a doubt he received you kindly, for, in fact, you returned without his permission". +7127-75946-0010 3.6 Your majesty's plan, then, in this affair, is 7127-75947-0016 7.48 Oh! mademoiselle, why have I not a devoted sister, or a true friend, such as yourself"? +7127-75947-0018 4.04 I have been here this quarter of an hour," replied La Valliere. 7127-75947-0024 7.33 Look yonder, do you not see the moon slowly rising, silvering the topmost branches of the chestnuts and the oaks. +7127-75947-0018 4.04 I have been here this quarter of an hour," replied La Valliere. 7127-75947-0025 5.57 exquisite soft turf of the woods, the happiness which your friendship confers upon me! +7127-75946-0025 3.96 The ballet began; the effect was more than beautiful. 7127-75947-0028 7.46 Quick, quick, then, among the high reed grass," said Montalais; "stoop, Athenais, you are so tall". +7127-75946-0025 3.96 The ballet began; the effect was more than beautiful. 7127-75947-0029 5.285 The young girls had, indeed, made themselves small - indeed invisible. +7127-75947-0019 3.875 Did not the dancing amuse you"? "No". 7127-75947-0032 4.745 Yes; but perhaps I frightened her". "In what way"? +7127-75947-0018 4.04 I have been here this quarter of an hour," replied La Valliere. 7127-75947-0035 4.415 Good gracious! has the king any right to interfere in matters of that kind? +7127-75947-0018 4.04 I have been here this quarter of an hour," replied La Valliere. 7127-75947-0037 8.824938 Oh! I am speaking seriously," replied Montalais, "and my opinion in this case is quite as good as the king's, I suppose; is it not, Louise"? +121-121726-0011 4.035 HUSBAND The next thing to a wife. 121-123859-0004 9.505 So I return rebuked to my content, And gain by ill thrice more than I have spent. +908-31957-0005 4.49 Alas, I have grieved so I am hard to love. 908-157963-0005 7.035 Like the doves voice, like transient day, like music in the air: Ah! +908-157963-0009 4.06 Why should the mistress of the vales of Har, utter a sigh. 908-157963-0006 8.11 And gentle sleep the sleep of death, and gently hear the voice Of him that walketh in the garden in the evening time. +908-157963-0029 3.63 Why a Tongue impressed with honey from every wind? 908-157963-0009 4.06 Why should the mistress of the vales of Har, utter a sigh. +908-31957-0018 3.915 But thou art not such A lover, my Beloved! 908-157963-0010 6.28 She ceasd and smiled in tears, then sat down in her silver shrine. +908-157963-0018 4.255 And fearest thou because I vanish and am seen no more. 908-157963-0013 4.315 And why it scatters its bright beauty thro the humid air. +908-157963-0018 4.255 And fearest thou because I vanish and am seen no more. 908-157963-0014 4.52 Descend O little cloud and hover before the eyes of Thel. +908-31957-0018 3.915 But thou art not such A lover, my Beloved! 908-157963-0016 5.105 I pass away, yet I complain, and no one hears my voice. +908-157963-0013 4.315 And why it scatters its bright beauty thro the humid air. 908-157963-0017 4.95 The Cloud then shewd his golden head and his bright form emerged. +908-157963-0003 3.08 Why fade these children of the spring? 908-157963-0018 4.255 And fearest thou because I vanish and am seen no more. +908-157963-0024 3.44 image of weakness, art thou but a Worm? 908-157963-0020 9.8 Till we arise linked in a golden band and never part: But walk united bearing food to all our tender flowers. +908-157963-0013 4.315 And why it scatters its bright beauty thro the humid air. 908-157963-0022 4.61 Come forth worm and the silent valley, to thy pensive queen. +908-157963-0002 2.755 why fades the lotus of the water? 908-157963-0023 9.625 The helpless worm arose and sat upon the Lillys leaf, And the bright Cloud saild on, to find his partner in the vale. +908-157963-0024 3.44 image of weakness, art thou but a Worm? 908-157963-0025 9.265 I see they lay helpless and naked: weeping And none to answer, none to cherish thee with mothers smiles. +908-157963-0029 3.63 Why a Tongue impressed with honey from every wind? 908-157963-0026 8.1 And says; Thou mother of my children, I have loved thee And I have given thee a crown that none can take away. +908-157963-0024 3.44 image of weakness, art thou but a Worm? 908-157963-0027 5.225 And lay me down in thy cold bed, and leave my shining lot. +908-157963-0003 3.08 Why fade these children of the spring? 908-157963-0028 4.955 Or an Eye of gifts and graces showring fruits and coined gold! +908-157963-0024 3.44 image of weakness, art thou but a Worm? 908-157963-0030 4.52 Why an Ear, a whirlpool fierce to draw creations in? +4446-2271-0003 3.7 It's been on only two weeks, and I've been half a dozen times already. 4446-2271-0001 6.35 He had preconceived ideas about everything, and his idea about Americans was that they should be engineers or mechanics. +4446-2275-0005 4.445 I felt it in my bones when I woke this morning that something splendid was going to turn up. 4446-2271-0008 5.495 Irene Burgoyne, one of her family, told me in confidence that there was a romance somewhere back in the beginning. +4446-2271-0005 3.395 She saves her hand, too. She's at her best in the second act. 4446-2271-0009 7.82 Mainhall vouched for her constancy with a loftiness that made Alexander smile, even while a kind of rapid excitement was tingling through him. +4446-2273-0009 4.015 It's not particularly rare," she said, "but some of it was my mother's. 4446-2271-0013 4.4 Do you know, I thought the dance a bit conscious to night, for the first time. +4446-2273-0002 3.295 Lamb wouldn't care a great deal about many of them, I fancy". 4446-2271-0014 5.34 Westmere and I were back after the first act, and we thought she seemed quite uncertain of herself. +4446-2273-0033 3.3 For a long time neither Hilda nor Bartley spoke. 4446-2271-0018 5.715 She considered a moment and then said "No, I think not, though I am glad you ask me. +4446-2275-0045 2.635 We've tortured each other enough for tonight. 4446-2271-0020 7.55 Of course," he reflected, "she always had that combination of something homely and sensible, and something utterly wild and daft. +4446-2271-0005 3.395 She saves her hand, too. She's at her best in the second act. 4446-2273-0000 8.995 Hilda was very nice to him, and he sat on the edge of his chair, flushed with his conversational efforts and moving his chin about nervously over his high collar. +4446-2273-0002 3.295 Lamb wouldn't care a great deal about many of them, I fancy". 4446-2273-0001 4.66 They asked him to come to see them in Chelsea, and they spoke very tenderly of Hilda. +4446-2273-0002 3.295 Lamb wouldn't care a great deal about many of them, I fancy". 4446-2273-0003 7.835 When Bartley arrived at Bedford Square on Sunday evening, Marie, the pretty little French girl, met him at the door and conducted him upstairs. +4446-2275-0022 3.28 But why didn't you tell me when you were here in the summer"? 4446-2273-0004 5.435 I should never have asked you if Molly had been here, for I remember you don't like English cookery". +4446-2273-0034 3.59 He felt a tremor run through the slender yellow figure in front of him. 4446-2273-0005 4.125 I haven't had a chance yet to tell you what a jolly little place I think this is. +4446-2273-0002 3.295 Lamb wouldn't care a great deal about many of them, I fancy". 4446-2273-0008 7.715 I've managed to save something every year, and that with helping my three sisters now and then, and tiding poor Cousin Mike over bad seasons. +4446-2271-0013 4.4 Do you know, I thought the dance a bit conscious to night, for the first time. 4446-2273-0009 4.015 It's not particularly rare," she said, "but some of it was my mother's. +4446-2271-0000 3.495 Mainhall liked Alexander because he was an engineer. 4446-2273-0015 4.505 Don't I, though! I'm so sorry to hear it. How did her son turn out? +4446-2271-0005 3.395 She saves her hand, too. She's at her best in the second act. 4446-2273-0016 9.645 Her hair is still like flax, and her blue eyes are just like a baby's, and she has the same three freckles on her little nose, and talks about going back to her bains de mer". +4446-2275-0015 2.98 He pulled up a window as if the air were heavy. 4446-2273-0021 5.255 What she wanted from us was neither our flowers nor our francs, but just our youth. +4446-2271-0013 4.4 Do you know, I thought the dance a bit conscious to night, for the first time. 4446-2273-0022 5.865 They were both remembering what the woman had said when she took the money: "God give you a happy love"! +4446-2273-0012 2.98 Thank you. But I don't like it so well as this". 4446-2273-0023 6.1 The strange woman, and her passionate sentence that rang out so sharply, had frightened them both. +4446-2271-0024 3.16 I shouldn't wonder if she could laugh about it with me now. 4446-2273-0024 4.825 Bartley started when Hilda rang the little bell beside her. "Dear me, why did you do that? +4446-2271-0011 3.945 Sir Harry Towne, mister Bartley Alexander, the American engineer". 4446-2273-0025 4.83 It was very jolly," he murmured lazily, as Marie came in to take away the coffee. +4446-2271-0013 4.4 Do you know, I thought the dance a bit conscious to night, for the first time. 4446-2273-0028 5.405 Nonsense. Of course I can't really sing, except the way my mother and grandmother did before me. +4446-2273-0011 2.79 There is nothing else that looks so jolly". 4446-2273-0032 7.835 He stood a little behind her, and tried to steady himself as he said: "It's soft and misty. See how white the stars are". +4446-2271-0012 3.78 I say, Sir Harry, the little girl's going famously to night, isn't she"? 4446-2273-0035 6.15 Bartley leaned over her shoulder, without touching her, and whispered in her ear: "You are giving me a chance"? "Yes. +1188-133604-0013 3.02 It must, remember, be one or the other. 1188-133604-0001 9.04 They unite every quality; and sometimes you will find me referring to them as colorists, sometimes as chiaroscurists. +1188-133604-0031 4.25 There's one, and there's another - the "Dudley" and the "Flint". 1188-133604-0005 8.56 It is the head of a parrot with a little flower in his beak from a picture of Carpaccio's, one of his series of the Life of Saint George. +1188-133604-0031 4.25 There's one, and there's another - the "Dudley" and the "Flint". 1188-133604-0010 6.095 But in this vignette, copied from Turner, you have the two principles brought out perfectly. +1188-133604-0040 3.23 The crampness and the poverty are all intended. 1188-133604-0014 4.39 Do not, therefore, think that the Gothic school is an easy one. +1188-133604-0031 4.25 There's one, and there's another - the "Dudley" and the "Flint". 1188-133604-0017 4.615 That a style is restrained or severe does not mean that it is also erroneous. +1188-133604-0014 4.39 Do not, therefore, think that the Gothic school is an easy one. 1188-133604-0022 9.63 You must look at him in the face - fight him - conquer him with what scathe you may: you need not think to keep out of the way of him. +1188-133604-0031 4.25 There's one, and there's another - the "Dudley" and the "Flint". 1188-133604-0025 7.45 You know I have just been telling you how this school of materialism and clay involved itself at last in cloud and fire. +1188-133604-0040 3.23 The crampness and the poverty are all intended. 1188-133604-0031 4.25 There's one, and there's another - the "Dudley" and the "Flint". +1188-133604-0006 2.4 Then he comes to the beak of it. 1188-133604-0033 6.625 Every plant in the grass is set formally, grows perfectly, and may be realized completely. +1188-133604-0014 4.39 Do not, therefore, think that the Gothic school is an easy one. 1188-133604-0036 7.97 In both these high mythical subjects the surrounding nature, though suffering, is still dignified and beautiful. +1188-133604-0031 4.25 There's one, and there's another - the "Dudley" and the "Flint". 1188-133604-0038 5.365 But now here is a subject of which you will wonder at first why Turner drew it at all. +1188-133604-0031 4.25 There's one, and there's another - the "Dudley" and the "Flint". 1188-133604-0039 6.625 It has no beauty whatsoever, no specialty of picturesqueness; and all its lines are cramped and poor. +1188-133604-0031 4.25 There's one, and there's another - the "Dudley" and the "Flint". 1188-133604-0043 4.885 See that your lives be in nothing worse than a boy's climbing for his entangled kite. +7729-102255-0000 3.285 The bogus Legislature numbered thirty six members. 7729-102255-0002 8.3 That summer's emigration, however, being mainly from the free States, greatly changed the relative strength of the two parties. +7729-102255-0034 2.71 To their sorrow they were soon undeceived. 7729-102255-0005 5.18 This was a formidable array of advantages; slavery was playing with loaded dice. +7729-102255-0013 2.675 It was, in fact, the best weapon of its day. 7729-102255-0010 8.54 Of the lynchings, the mobs, and the murders, it would be impossible, except in a very extended work, to note the frequent and atrocious details. +7729-102255-0034 2.71 To their sorrow they were soon undeceived. 7729-102255-0012 4.075 Several hundred free State men promptly responded to the summons. +7729-102255-0034 2.71 To their sorrow they were soon undeceived. 7729-102255-0014 5.295 The leaders of the conspiracy became distrustful of their power to crush the town. +7729-102255-0001 3.45 This was at the March election, eighteen fifty five. 7729-102255-0021 7.93 But the affair was magnified as a crowning proof that the free State men were insurrectionists and outlaws. +7729-102255-0001 3.45 This was at the March election, eighteen fifty five. 7729-102255-0023 5.5 Their distinctive characters, however, display one broad and unfailing difference. +7729-102255-0001 3.45 This was at the March election, eighteen fifty five. 7729-102255-0025 5.485 Their assumed character changed with their changing opportunities or necessities. +7729-102255-0001 3.45 This was at the March election, eighteen fifty five. 7729-102255-0028 9.6 Private persons who had leased the Free State Hotel vainly besought the various authorities to prevent the destruction of their property. +7729-102255-0001 3.45 This was at the March election, eighteen fifty five. 7729-102255-0029 7.06 Ten days were consumed in these negotiations; but the spirit of vengeance refused to yield. +7729-102255-0001 3.45 This was at the March election, eighteen fifty five. 7729-102255-0030 7.25 He summoned half a dozen citizens to join his posse, who followed, obeyed, and assisted him. +7729-102255-0001 3.45 This was at the March election, eighteen fifty five. 7729-102255-0031 6.75 He continued his pretended search and, to give color to his errand, made two arrests. +7729-102255-0001 3.45 This was at the March election, eighteen fifty five. 7729-102255-0033 6.775 As he had promised to protect the hotel, the reassured citizens began to laugh at their own fears. +7729-102255-0034 2.71 To their sorrow they were soon undeceived. 7729-102255-0035 5.625 The military force, partly rabble, partly organized, had meanwhile moved into the town. +7729-102255-0012 4.075 Several hundred free State men promptly responded to the summons. 7729-102255-0036 7.705 He planted a company before the hotel, and demanded a surrender of the arms belonging to the free- State military companies. +7729-102255-0001 3.45 This was at the March election, eighteen fifty five. 7729-102255-0038 7.92 Atchison, who had been haranguing the mob, planted his two guns before the building and trained them upon it. +7729-102255-0001 3.45 This was at the March election, eighteen fifty five. 7729-102255-0039 6.815 The inmates being removed, at the appointed hour a few cannon balls were fired through the stone walls. +7729-102255-0001 3.45 This was at the March election, eighteen fifty five. 7729-102255-0045 6.805 Captain Martin said: 'I shall give you a pistol to help protect yourself if worse comes to worst! +3570-5694-0022 4.295 The livery becomes obnoxious to nearly all who are required to wear it. 3570-5695-0000 4.83 In a general way, though not wholly nor consistently, these two groups coincide. +3570-5694-0012 3.205 There is a more or less elaborate system of rank and grades. 3570-5695-0002 7.805 But as we descend the social scale, the point is presently reached where the duties of vicarious leisure and consumption devolve upon the wife alone. +3570-5694-0012 3.205 There is a more or less elaborate system of rank and grades. 3570-5695-0003 5.355 In the communities of the Western culture, this point is at present found among the lower middle class. +3570-5694-0019 3.755 But the general distinction is not on that account to be overlooked. 3570-5695-0006 7.47 Very much of squalor and discomfort will be endured before the last trinket or the last pretense of pecuniary decency is put away. +3570-5694-0019 3.755 But the general distinction is not on that account to be overlooked. 3570-5695-0007 9.755 There is no class and no country that has yielded so abjectly before the pressure of physical want as to deny themselves all gratification of this higher or spiritual need. +3570-5694-0019 3.755 But the general distinction is not on that account to be overlooked. 3570-5695-0008 6.845 The question is, which of the two methods will most effectively reach the persons whose convictions it is desired to affect. +3570-5694-0019 3.755 But the general distinction is not on that account to be overlooked. 3570-5695-0009 5.025 Each will therefore serve about equally well during the earlier stages of social growth. +3570-5694-0019 3.755 But the general distinction is not on that account to be overlooked. 3570-5695-0010 4.665 The modern organization of industry works in the same direction also by another line. +3570-5696-0006 4.16 As used in the speech of everyday life the word carries an undertone of deprecation. 3570-5695-0011 8.26 It is evident, therefore, that the present trend of the development is in the direction of heightening the utility of conspicuous consumption as compared with leisure. +3570-5696-0006 4.16 As used in the speech of everyday life the word carries an undertone of deprecation. 3570-5695-0013 4.64 Consumption becomes a larger element in the standard of living in the city than in the country. +3570-5694-0012 3.205 There is a more or less elaborate system of rank and grades. 3570-5695-0015 7.95 The result is a great mobility of the labor employed in printing; perhaps greater than in any other equally well defined and considerable body of workmen. +260-123440-0008 3.745 I'll try if I know all the things I used to know. 260-123288-0001 5.08 The weather - if we may use that term - will change before long. +260-123288-0020 2.9 Each of us is lashed to some part of the raft. 260-123288-0002 7.25 The atmosphere is charged with vapours, pervaded with the electricity generated by the evaporation of saline waters. +260-123288-0009 3.435 Those clouds seem as if they were going to crush the sea". 260-123288-0003 8.905 The electric light can scarcely penetrate through the dense curtain which has dropped over the theatre on which the battle of the elements is about to be waged. +260-123286-0020 3.06 Tuesday, august eighteenth. 260-123288-0004 4.31 The air is heavy; the sea is calm. +260-123440-0005 3.105 And yesterday things went on just as usual. 260-123288-0006 4.88 The atmosphere is evidently charged and surcharged with electricity. +260-123440-0008 3.745 I'll try if I know all the things I used to know. 260-123288-0008 5.515 There's a heavy storm coming on," I cried, pointing towards the horizon. +260-123440-0006 2.715 I wonder if I've been changed in the night? 260-123288-0011 8.98 But if we have now ceased to advance why do we yet leave that sail loose, which at the first shock of the tempest may capsize us in a moment? +260-123288-0019 2.955 At noon the violence of the storm redoubles. 260-123288-0016 4.865 I refer to the thermometer; it indicates... (the figure is obliterated). +260-123440-0006 2.715 I wonder if I've been changed in the night? 260-123288-0017 5.225 Is the atmospheric condition, having once reached this density, to become final? +260-123440-0005 3.105 And yesterday things went on just as usual. 260-123288-0027 6.305 A suffocating smell of nitrogen fills the air, it enters the throat, it fills the lungs. +8455-210777-0062 3.05 When do you intend that the John Bright shall start"? 8455-210777-0000 8.745 I remained there alone for many hours, but I must acknowledge that before I left the chambers I had gradually brought myself to look at the matter in another light. +8455-210777-0026 3 And the death of which I dreamt could not, alas! 8455-210777-0002 6.24 On arriving at home at my own residence, I found that our salon was filled with a brilliant company. +8455-210777-0026 3 And the death of which I dreamt could not, alas! 8455-210777-0005 5.685 We have our little struggles here as elsewhere, and all things cannot be done by rose water. +8455-210777-0047 2.54 You propose to kidnap me," I said. 8455-210777-0006 4.52 We are quite satisfied now, Captain Battleax," said my wife. +8455-210777-0049 4.11 Lieutenant Crosstrees is a very gallant officer. 8455-210777-0009 4.58 No doubt, in process of time the ladies will follow +8455-210777-0025 3.63 What could I do now but just lay myself down and die? 8455-210777-0011 6.63 I did not mean," said Captain Battleax, "to touch upon public subjects at such a moment as this. +8455-210777-0050 3.945 One of us always remains on board while the other is on shore. 8455-210777-0013 7.41 Jack had been standing in the far corner of the room talking to Eva, and was now reduced to silence by his praises. +8455-210777-0066 2.76 They, of course, must all be altered". 8455-210777-0014 4.12 Sir Kennington Oval is a very fine player," said my wife. +8455-210777-0014 4.12 Sir Kennington Oval is a very fine player," said my wife. 8455-210777-0015 8.615 I and my wife and son, and the two Craswellers, and three or four others, agreed to dine on board the ship on the next. +8455-210777-0026 3 And the death of which I dreamt could not, alas! 8455-210777-0017 5.330063 My wife, on the spur of the moment, managed to give the gentlemen a very good dinner. +8455-210777-0025 3.63 What could I do now but just lay myself down and die? 8455-210777-0018 5.925 This, she said, was true hospitality; and I am not sure that I did not agree with her. +8455-210777-0062 3.05 When do you intend that the John Bright shall start"? 8455-210777-0019 8.105 Then there were three or four leading men of the community, with their wives, who were for the most part the fathers and mothers of the young ladies. +8455-210777-0026 3 And the death of which I dreamt could not, alas! 8455-210777-0023 4.73 We sat with the officers some little time after dinner, and then went ashore. +8455-210777-0043 3.145 But what is the delicate mission"? I asked. 8455-210777-0024 7.56 How much of evil, - of real accomplished evil, - had there not occurred to me during the last few days! +8455-210777-0068 2.59 Your power is sufficient," I said. 8455-210777-0028 7.735 Jack would become Eva's happy husband, and would remain amidst the hurried duties of the eager world. +8455-210777-0025 3.63 What could I do now but just lay myself down and die? 8455-210777-0031 7.67 You have received us with all that courtesy and hospitality for which your character in England stands so high. +8455-210777-0026 3 And the death of which I dreamt could not, alas! 8455-210777-0033 7.51 But your power is so superior to any that I can advance, as to make us here feel that there is no disgrace in yielding to it. +8455-210777-0050 3.945 One of us always remains on board while the other is on shore. 8455-210777-0034 7.7 Not a doubt but had your force been only double or treble our own, I should have found it my duty to struggle with you. +8455-210777-0068 2.59 Your power is sufficient," I said. 8455-210777-0037 4.735 You have come to us threatening us with absolute destruction. +8455-210777-0026 3 And the death of which I dreamt could not, alas! 8455-210777-0039 5.59 I can assure you he has not even allowed me to see the trigger since I have been on board. +8455-210777-0025 3.63 What could I do now but just lay myself down and die? 8455-210777-0040 6.195 Then," said Sir Ferdinando, "there is nothing for it but that he must take you with him". +8455-210777-0026 3 And the death of which I dreamt could not, alas! 8455-210777-0041 6.37 There came upon me a sudden shock when I heard these words, which exceeded anything which I had yet felt. +8455-210777-0050 3.945 One of us always remains on board while the other is on shore. 8455-210777-0044 7.17 I was to be taken away and carried to England or elsewhere, - or drowned upon the voyage, it mattered not which. +8455-210777-0062 3.05 When do you intend that the John Bright shall start"? 8455-210777-0046 9.33 You may be quite sure it's there," said Captain Battleax, "and that I can so use it as to half obliterate your town within two minutes of my return on board". +8455-210777-0020 3.155 Oh yes," said Jack, "and I'm nowhere. 8455-210777-0049 4.11 Lieutenant Crosstrees is a very gallant officer. +8455-210777-0048 3.43 What would become of your gun were I to kidnap you"? 8455-210777-0052 4.94 You will allow me to suggest," said he, "that that is a matter of opinion. +8455-210777-0062 3.05 When do you intend that the John Bright shall start"? 8455-210777-0053 6.955 Were I to comply with your orders without expressing my own opinion, I should seem to have done so willingly hereafter. +8455-210777-0025 3.63 What could I do now but just lay myself down and die? 8455-210777-0055 9.555 SIR, - I have it in command to inform your Excellency that you have been appointed Governor of the Crown colony which is called Britannula. +8455-210777-0025 3.63 What could I do now but just lay myself down and die? 8455-210777-0056 5.545 The peculiar circumstances of the colony are within your Excellency's knowledge. +8455-210777-0050 3.945 One of us always remains on board while the other is on shore. 8455-210777-0058 7.16 It is founded on the acknowledged weakness of those who survive that period of life at which men cease to work. +8455-210777-0064 3.835 And I have no one ready to whom I can give up the archives of the Government". 8455-210777-0059 5.535 But it is surmised that you will find difficulties in the way of your entering at once upon your government. +8455-210777-0062 3.05 When do you intend that the John Bright shall start"? 8455-210777-0060 7.075 The John Bright is armed with a weapon of great power, against which it is impossible that the people of Britannula should prevail. +8455-210777-0064 3.835 And I have no one ready to whom I can give up the archives of the Government". 8455-210777-0069 8.915 If you will give us your promise to meet Captain Battleaxe here at this time tomorrow, we will stretch a point and delay the departure of the John Bright for twenty four hours". +8455-210777-0026 3 And the death of which I dreamt could not, alas! 8455-210777-0070 5.945 And this plan was adopted, too, in order to extract from me a promise that I would depart in peace. +6829-68769-0043 2.59 And he deserves a term in state's prison". 6829-68771-0002 8.94 The "weak kneed" contingency must be strengthened and fortified, and a couple of hundred votes in one way or another secured from the opposition. +6829-68769-0016 4.12 He unlocked the door, and called: "Here's visitors, Tom". 6829-68771-0003 4.015 The Democratic Committee figured out a way to do this. +6829-68769-0014 3.655 They followed the jailer along a succession of passages. 6829-68771-0004 8.44 Under ordinary conditions Reynolds was sure to be elected, but the Committee proposed to sacrifice him in order to elect Hopkins. +6829-68769-0037 2.53 I've seen lots of that kind in my day. 6829-68771-0005 6.165 The only thing necessary was to "fix" Seth Reynolds, and this Hopkins arranged personally. +6829-68769-0012 4.295 Oh, say! that's different," observed Markham, altering his demeanor. 6829-68771-0006 5.92 And this was why Kenneth and Beth discovered him conversing with the young woman in the buggy. +6829-68769-0039 4.045 He looked up rather ungraciously, but motioned them to be seated. 6829-68771-0008 7.18 These women were flattered by the attention of the young lady and had promised to assist in electing mister Forbes. +6829-68769-0051 3.545 There was a grim smile of amusement on his shrewd face. 6829-68771-0010 9.82 The Fairview band was engaged to discourse as much harmony as it could produce, and the resources of the great house were taxed to entertain the guests. +6829-68769-0037 2.53 I've seen lots of that kind in my day. 6829-68771-0011 5.625 Tables were spread on the lawn and a dainty but substantial repast was to be served. +6829-68769-0028 3.29 He is supposed to sign all the checks of the concern. 6829-68771-0014 4.77 We ought to have more attendants, Beth," said Louise, approaching her cousin. +6829-68769-0033 4.02 It was better for him to think the girl unfeeling than to know the truth. 6829-68771-0015 4.525 Won't you run into the house and see if Martha can't spare one or two more maids"? +6829-68769-0035 2.755 It won't be much, but I'm grateful to find a friend. 6829-68771-0016 6.99 She was very fond of the young ladies, whom she had known when Aunt Jane was the mistress here, and Beth was her especial favorite. +6829-68771-0021 2.61 But it can't be," protested the girl. 6829-68771-0018 8.445 For a moment Beth stood staring, while the new maid regarded her with composure and a slight smile upon her beautiful face. +6829-68771-0031 2.515 Her eyes wandered to the maid's hands. 6829-68771-0019 7.42 She was dressed in the regulation costume of the maids at Elmhurst, a plain black gown with white apron and cap. +6829-68771-0022 3.8 I attend to the household mending, you know, and care for the linen. 6829-68771-0020 4.615 Then she gave a little laugh, and replied: "No, Miss Beth. I'm Elizabeth Parsons". +6829-68769-0012 4.295 Oh, say! that's different," observed Markham, altering his demeanor. 6829-68771-0023 5.425 You speak like an educated person," said Beth, wonderingly. "Where is your home"? +6829-68771-0035 4.39 Will you leave me alone in my own room, or must I go away to escape you"? 6829-68771-0024 6.245 For the first time the maid seemed a little confused, and her gaze wandered from the face of her visitor. +6829-68769-0051 3.545 There was a grim smile of amusement on his shrewd face. 6829-68771-0025 7.83 She sat down in a rocking chair, and clasping her hands in her lap, rocked slowly back and forth. "I'm sorry," said Beth. +6829-68769-0051 3.545 There was a grim smile of amusement on his shrewd face. 6829-68771-0027 5.32 They - they excite me, in some way, and I - I can't bear them. You must excuse me". +6829-68771-0035 4.39 Will you leave me alone in my own room, or must I go away to escape you"? 6829-68771-0029 8.945 Beth was a beautiful girl - the handsomest of the three cousins, by far; yet Eliza surpassed her in natural charm, and seemed well aware of the fact. +6829-68769-0003 4.215 It was a deliberate theft from his employers to protect a girl he loved. 6829-68771-0030 6.225 Her manner was neither independent nor assertive, but rather one of well bred composure and calm reliance. +6829-68769-0002 3.075 I can't see it in that light," said the old lawyer. 6829-68771-0032 6.555 However her features and form might repress any evidence of nervousness, these hands told a different story. +6829-68771-0034 2.475 I wish I knew myself," she cried, fiercely. 6829-68771-0033 5.45 She rose quickly to her feet, with an impetuous gesture that made her visitor catch her breath. +6829-68769-0002 3.075 I can't see it in that light," said the old lawyer. 6829-68771-0035 4.39 Will you leave me alone in my own room, or must I go away to escape you"? +6829-68769-0028 3.29 He is supposed to sign all the checks of the concern. 6829-68771-0036 5.2 Eliza closed the door behind her with a decided slam, and a key clicked in the lock. +8463-287645-0008 3.325 As usual nothing was done in the way of punishment". 8463-287645-0000 4.73 This was what did the mischief so far as the "running away" was concerned. +8463-294828-0008 2.65 And yet, what a fine, gallant lad! 8463-287645-0003 7.905 Of this party, Edward, a boy of seventeen, called forth much sympathy; he too was claimed by Hollan. +8463-294828-0026 2.745 We have a commander who's game for anything"! 8463-287645-0006 7.71 The doctor who attended the injured creature in this case was simply told that she slipped and fell down stairs as she was coming down. +8463-294828-0021 2.735 A route slightly less direct, that's all. 8463-287645-0010 4.325 He worked me very hard; he wanted to be beating me all the time". +8463-287645-0008 3.325 As usual nothing was done in the way of punishment". 8463-287645-0011 6.38 She was a large, homely woman; they were common white people, with no reputation in the community". +8463-294828-0011 3.91 He went here, there, and everywhere in perfect contentment. 8463-287645-0012 5.425 Substantially this was Jacob's unvarnished description of his master and mistress. +8463-294828-0032 4.395 In person. Welcome aboard, professor. Your cabin is waiting for you". 8463-287645-0013 6.665 As to his age, and also the name of his master, Jacob's statement varied somewhat from the advertisement. +3729-6852-0016 4.195 Madame Quinson, besides, can answer your enquiries. 3729-6852-0011 7.37 I had a name, I believe, in my young days, but I have forgotten it since I have been in service. +3729-6852-0010 2.755 I never had any family. 3729-6852-0014 5.71 Here, go and get me change for a Louis". "I have it, sir". +3729-6852-0025 3 Is there not a meridian everywhere"? 3729-6852-0016 4.195 Madame Quinson, besides, can answer your enquiries. +3729-6852-0016 4.195 Madame Quinson, besides, can answer your enquiries. 3729-6852-0018 6.21 I sit down at a small table: a waiter comes immediately to enquire my wishes. +3729-6852-0019 3.305 I tell him to give me some coffee, if it is good. 3729-6852-0022 8.315 I address him in Italian, and he answers very wittily, but his way of speaking makes me smile, and I tell him why. +3729-6852-0019 3.305 I tell him to give me some coffee, if it is good. 3729-6852-0023 8.185 My remark pleases him, but I soon prove to him that it is not the right way to speak, however perfect may have been the language of that ancient writer. +3729-6852-0019 3.305 I tell him to give me some coffee, if it is good. 3729-6852-0024 5.515 I see a crowd in one corner of the garden, everybody standing still and looking up. +3729-6852-0016 4.195 Madame Quinson, besides, can answer your enquiries. 3729-6852-0026 4.69 Yes, but the meridian of the Palais Royal is the most exact". +3729-6852-0019 3.305 I tell him to give me some coffee, if it is good. 3729-6852-0028 5.265 All these honest persons are waiting their turn to get their snuff boxes filled". +3729-6852-0010 2.755 I never had any family. 3729-6852-0029 8.605 It is sold everywhere, but for the last three weeks nobody will use any snuff but that sold at the 'Civet Cat. +3729-6852-0025 3 Is there not a meridian everywhere"? 3729-6852-0031 4.4 But how did she manage to render it so fashionable"? +3729-6852-0019 3.305 I tell him to give me some coffee, if it is good. 3729-6852-0037 5.89 She introduced me to all her guests, and gave me some particulars respecting every one of them. +3729-6852-0021 2.96 I thank him and take my leave. 3729-6852-0038 5.77 What, sir"! I said to him, "am I fortunate enough to see you? +3729-6852-0019 3.305 I tell him to give me some coffee, if it is good. 3729-6852-0039 8.825 He himself recited the same passage in French, and politely pointed out the parts in which he thought that I had improved on the original. +3729-6852-0019 3.305 I tell him to give me some coffee, if it is good. 3729-6852-0044 6.98 I will make you translate them into French, and you need not be afraid of my finding you insatiable". +7176-92135-0026 2.95 Enter Hamlet with his favourite boar hound. 7176-88083-0000 5.695 All about him was a tumult of bright and broken color, scattered in broad splashes. +7176-92135-0039 3.125 Tea, please, Matthews. Butler (impassively). 7176-88083-0002 7.51 His feet were red, his long narrow beak, with its saw toothed edges and sharp hooked tip, was bright red. +7176-92135-0024 4.1 To be or not to be, that is the question; whether 'tis nobler 7176-88083-0003 7.6 But here he was at a terrible disadvantage as compared with the owls, hawks, and eagles. He had no rending claws. +7176-88083-0016 3.92 Straightway the hawk glided from his perch and darted after him. 7176-88083-0004 7.5 But suddenly, straight and swift as a diving cormorant, he shot down into the torrent and disappeared beneath the surface. +7176-88083-0016 3.92 Straightway the hawk glided from his perch and darted after him. 7176-88083-0005 4.7 Once fairly a wing, however, he wheeled and made back hurriedly for his perch. +7176-92135-0008 4.43 Lend me your ear for ten minutes, and you shall learn just what stagecraft is". 7176-88083-0006 4.295 It might have seemed that a trout of this size was a fairly substantial meal. +7176-92135-0008 4.43 Lend me your ear for ten minutes, and you shall learn just what stagecraft is". 7176-88083-0009 4.045 The great hawk followed hurriedly, to retrieve his prey from the ground. +7176-88083-0008 3.28 In despair he hurled himself downward too soon. 7176-88083-0010 6.74 The cat growled softly, picked up the prize in her jaws and trotted into the bushes to devour it. +7176-88083-0016 3.92 Straightway the hawk glided from his perch and darted after him. 7176-88083-0012 5.045 The hawk alighted on the dead branch, and sat upright, motionless, as if surprised. +7176-88083-0009 4.045 The great hawk followed hurriedly, to retrieve his prey from the ground. 7176-88083-0014 4.67 The hawk sat upon the branch and watched his quarry swimming beneath the surface. +7176-88083-0016 3.92 Straightway the hawk glided from his perch and darted after him. 7176-88083-0019 5.81 As he flew, his down reaching, clutching talons were not half a yard above the fugitive's head. +7176-88083-0009 4.045 The great hawk followed hurriedly, to retrieve his prey from the ground. 7176-88083-0020 5.415 Where the waves for an instant sank, they came closer, - but not quite within grasping reach. +7176-92135-0024 4.1 To be or not to be, that is the question; whether 'tis nobler 7176-88083-0022 9.485 The hawk, embittered by the loss of his first quarry, had become as dogged in pursuit as a weasel, not to be shaken off or evaded or deceived. +7176-88083-0016 3.92 Straightway the hawk glided from his perch and darted after him. 7176-88083-0023 9.645 He had a lot of line out, and the place was none too free for a long cast; but he was impatient to drop his flies again on the spot where the big fish was feeding. +7176-92135-0024 4.1 To be or not to be, that is the question; whether 'tis nobler 7176-88083-0024 8.195 The last drop fly, as luck would have it, caught just in the corner of the hawk's angrily open beak, hooking itself firmly. +7176-88083-0006 4.295 It might have seemed that a trout of this size was a fairly substantial meal. 7176-88083-0025 7.38 At the sudden sharp sting of it, the great bird turned his head and noticed, for the first time, the fisherman standing on the bank. +7176-88083-0009 4.045 The great hawk followed hurriedly, to retrieve his prey from the ground. 7176-88083-0026 5.53 The drag upon his beak and the light check upon his wings were inexplicable to him, and appalling. +7127-75947-0008 4.155 The arrow pierced his heart and wounded him mortally. 7127-75946-0004 4.49 Certainly, sire; but I must have money to do that". "What! +7127-75947-0035 4.415 Good gracious! has the king any right to interfere in matters of that kind? 7127-75946-0006 7.98 He has given them with too much grace not to have others still to give, if they are required, which is the case at the present moment. +7127-75947-0017 2.665 What, already here"! they said to her. 7127-75946-0007 4.755 It is necessary, therefore, that he should comply". The king frowned. +7127-75947-0030 2.76 She was here just now," said the count. 7127-75946-0008 4.46 Does your majesty then no longer believe the disloyal attempt"? +7127-75946-0005 2.67 What do you mean"? inquired Louis, 7127-75946-0009 4.72 Not at all; you are, on the contrary, most agreeable to me". +7127-75947-0011 3.62 Remain, I implore you: the evening is most lovely. 7127-75946-0012 9.87 The news circulated with the rapidity of lightning; during its progress it kindled every variety of coquetry, desire, and wild ambition. +7127-75947-0002 3.235 Do you think so"? she replied with indifference. 7127-75946-0013 8.58 The king had completed his toilette by nine o'clock; he appeared in an open carriage decorated with branches of trees and flowers. +7127-75947-0018 4.04 I have been here this quarter of an hour," replied La Valliere. 7127-75946-0015 7.515 Suddenly, for the purpose of restoring peace and order, Spring, accompanied by his whole court, made his appearance. +7127-75947-0018 4.04 I have been here this quarter of an hour," replied La Valliere. 7127-75946-0018 9.14 There was something in his carriage which resembled the buoyant movements of an immortal, and he did not dance so much as seem to soar along. +7127-75947-0018 4.04 I have been here this quarter of an hour," replied La Valliere. 7127-75946-0020 6.52 Far from it, sire; your majesty having given no directions about it, the musicians have retained it". +7127-75947-0002 3.235 Do you think so"? she replied with indifference. 7127-75946-0024 5.09 Monsieur was the only one who did not understand anything about the matter. +7127-75947-0018 4.04 I have been here this quarter of an hour," replied La Valliere. 7127-75946-0027 9.675 Disdainful of a success of which Madame showed no acknowledgement, he thought of nothing but boldly regaining the marked preference of the princess. +7127-75946-0023 3.745 The king seemed only pleased with every one present. 7127-75946-0029 9.285 The king, who had from this moment become in reality the principal dancer in the quadrille, cast a look upon his vanquished rival. +5105-28240-0018 2.885 You will take me on board, count, will you not"? 5105-28241-0000 6.455 Her sea going qualities were excellent, and would have amply sufficed for a circumnavigation of the globe. +5105-28240-0016 4.17 To all these inquiries, the count responded in the affirmative. 5105-28241-0005 8.415 For a few miles she followed the line hitherto presumably occupied by the coast of Algeria; but no land appeared to the south. +5105-28233-0001 4.49 He seemed born to please without being conscious of the power he possessed. 5105-28241-0006 7.55 The log and the compass, therefore, were able to be called upon to do the work of the sextant, which had become utterly useless. +5105-28240-0002 4.01 exclaimed Servadac, keeping his eye unmoved at his telescope. 5105-28241-0008 8.54 The earth has undoubtedly entered upon a new orbit, but she is not incurring any probable risk of being precipitated onto the sun". +5105-28240-0013 2.96 Nothing more than you know yourself". 5105-28241-0009 7.01 And what demonstration do you offer," asked Servadac eagerly, "that it will not happen"? +5105-28240-0010 2.935 Captain Servadac hastened towards him. 5105-28241-0012 6.775 Is it not impossible," he murmured aloud, "that any city should disappear so completely? +5105-28241-0014 2.995 Another circumstance was most remarkable. 5105-28241-0013 4.82 Would not the loftiest eminences of the city at least be visible? +5105-28240-0018 2.885 You will take me on board, count, will you not"? 5105-28241-0016 6.285 You must see, lieutenant, I should think, that we are not so near the coast of Algeria as you imagined". +5105-28240-0018 2.885 You will take me on board, count, will you not"? 5105-28241-0019 5.29 Nothing was to be done but to put about, and return in disappointment towards the north. +7021-85628-0004 2.805 Yes, why not"? thought Anders. 7021-79759-0000 4.775 Nature of the Effect produced by Early Impressions. +7021-79740-0009 3.635 They were now playing with their dolls in the parlor. 7021-79759-0002 5.25 They are chiefly formed from combinations of the impressions made in childhood. +7021-79759-0001 2.48 That is comparatively nothing. 7021-79759-0003 4.62 Vast Importance and Influence of this mental Furnishing, +1320-122617-0041 4.15 Uncas cast his skin, and stepped forth in his own beautiful proportions. 1320-122612-0001 9.52 The dews were suffered to exhale, and the sun had dispersed the mists, and was shedding a strong and clear light in the forest, when the travelers resumed their journey. +1320-122612-0014 3.515 The examination, however, resulted in no discovery. 1320-122612-0002 7.46 After proceeding a few miles, the progress of Hawkeye, who led the advance, became more deliberate and watchful. +1320-122617-0005 4.4 The bear shook his shaggy sides, and then a well known voice replied: 1320-122612-0003 9.865 He often stopped to examine the trees; nor did he cross a rivulet without attentively considering the quantity, the velocity, and the color of its waters. +1320-122617-0005 4.4 The bear shook his shaggy sides, and then a well known voice replied: 1320-122612-0004 6.425 Distrusting his own judgment, his appeals to the opinion of Chingachgook were frequent and earnest. +1320-122612-0009 3.88 It would have been more wonderful had he spoken without a bidding. 1320-122612-0005 5.915 Yet here are we, within a short range of the Scaroons, and not a sign of a trail have we crossed! +1320-122617-0030 3.98 So choose for yourself to make a rush or tarry here". 1320-122612-0006 4.845 Let us retrace our steps, and examine as we go, with keener eyes. +1320-122612-0014 3.515 The examination, however, resulted in no discovery. 1320-122612-0007 5.54 Chingachgook had caught the look, and motioning with his hand, he bade him speak. +1320-122612-0009 3.88 It would have been more wonderful had he spoken without a bidding. 1320-122612-0008 7.875 The eyes of the whole party followed the unexpected movement, and read their success in the air of triumph that the youth assumed. +1320-122612-0009 3.88 It would have been more wonderful had he spoken without a bidding. 1320-122612-0013 6.55 A circle of a few hundred feet in circumference was drawn, and each of the party took a segment for his portion. +1320-122617-0041 4.15 Uncas cast his skin, and stepped forth in his own beautiful proportions. 1320-122612-0015 6.385 The whole party crowded to the spot where Uncas pointed out the impression of a moccasin in the moist alluvion. +5142-33396-0028 3.755 On a bench in a far corner were a dozen people huddled together. 5142-33396-0001 5.02 What is your country, Olaf? Have you always been a thrall"? The thrall's eyes flashed. +5142-33396-0010 3.455 In the stern I curved the tail up almost as high as the head. 5142-33396-0006 6.23 I made her for only twenty oars because I thought few men would follow me; for I was young, fifteen years old. +5142-33396-0003 3.47 The rest of you, off a viking'! "He had three ships. 5142-33396-0007 4.975 At the prow I carved the head with open mouth and forked tongue thrust out. +5142-33396-0050 2.885 May you drink heart's ease from it for many years. 5142-33396-0012 4.59 Then I will get me a farm and will winter in that land. Now who will follow me? +5142-33396-0021 3.505 Up and down the water we went to get much wealth and much frolic. 5142-33396-0015 4.31 As our boat flashed down the rollers into the water I made this song and sang it: +5142-33396-0014 3.245 Thirty men, one after another, raised their horns and said: 5142-33396-0019 4.985 Oh! it is better to live on the sea and let other men raise your crops and cook your meals. +5142-33396-0036 4.26 So I will give out this law: that my men shall never leave you alone. 5142-33396-0022 4.77 What of the farm, Olaf'? "'Not yet,' I answered. 'Viking is better for summer. +5142-33396-0047 2.535 My men pounded the table with their fists. 5142-33396-0024 5.345 I stood with my back to the wall; for I wanted no sword reaching out of the dark for me. +5142-33396-0037 3.575 Hakon there shall be your constant companion, friend farmer. 5142-33396-0031 7.845 They set up a crane over the fire and hung the pot upon it, and we sat and watched it boil while we joked. At last the supper began. +5142-33396-0010 3.455 In the stern I curved the tail up almost as high as the head. 5142-33396-0032 9.785 The farmer sat gloomily on the bench and would not eat, and you cannot wonder; for he saw us putting potfuls of his good beef and basket loads of bread into our big mouths. +5142-33396-0050 2.885 May you drink heart's ease from it for many years. 5142-33396-0033 5.28 You would not eat with us. You cannot say no to half of my ale. I drink this to your health. +5142-33396-0009 3.37 There, stand so'! I said, 'and glare and hiss at my foes. 5142-33396-0034 6.615 Then I drank half of the hornful and sent the rest across the fire to the farmer. He took it and smiled, saying: +5142-36586-0000 3.65 It is manifest that man is now subject to much variability. 5142-33396-0036 4.26 So I will give out this law: that my men shall never leave you alone. +5142-33396-0060 2.615 Take him out, Thorkel, and let him taste your sword. 5142-33396-0038 4.18 He shall not leave you day or night, whether you are working or playing or sleeping. +5142-33396-0030 2.765 The thralls were bringing in a great pot of meat. 5142-33396-0042 6.095 So no tales got out to the neighbors. Besides, it was a lonely place, and by good luck no one came that way. +5142-33396-0030 2.765 The thralls were bringing in a great pot of meat. 5142-33396-0044 4.855 I am stiff with long sitting,' he said. 'I itch for a fight'. "I turned to the farmer. +5142-33396-0014 3.245 Thirty men, one after another, raised their horns and said: 5142-33396-0051 5.57 And with it I leave you a name, Sif the Friendly. I shall hope to drink with you sometime in Valhalla. +5142-33396-0060 2.615 Take him out, Thorkel, and let him taste your sword. 5142-33396-0052 5.88 Here is a ring for Sif the Friendly'. "'And here is a bracelet'. "'A sword would not be ashamed to hang at your side. +5142-33396-0049 3.305 Here, friend, take it,' and he thrust it into the farmer's hand. 5142-33396-0054 5.745 That is the best way to decide, for the spear will always point somewhere, and one thing is as good as another. +5142-33396-0050 2.885 May you drink heart's ease from it for many years. 5142-33396-0059 5.47 Yes. And with all your fingers it took you a year to catch me'. "The king frowned more angrily. +5142-33396-0025 3.32 Come, come'! I called, when no one obeyed. 'A fire! 5142-33396-0065 5.195 Soft heart'! he said gently to her; then to Thorkel, 'Well, let him go, Thorkel! +5142-33396-0049 3.305 Here, friend, take it,' and he thrust it into the farmer's hand. 5142-33396-0067 5.565 But, young sharp tongue, now that we have caught you we will put you into a trap that you cannot get out of. +5683-32866-0000 2.645 Miss Lake declined the carriage to night. 5683-32879-0000 8.92 It was not very much past eleven that morning when the pony carriage from Brandon drew up before the little garden wicket of Redman's Farm. +5683-32879-0022 4.175 I like you still, Rachel; I'm sure I'll always like you. 5683-32879-0003 9.345 Women can hide their pain better than we men, and bear it better, too, except when shame drops fire into the dreadful chalice. +5683-32866-0001 3.47 And he added something still less complimentary. 5683-32879-0005 6.11 This transient spring and lighting up are beautiful - a glamour beguiling our senses. +5683-32865-0001 2.58 said Lord Chelford, addressing me. 5683-32879-0007 6.795 Rachel's pale and sharpened features and dilated eye struck her with a painful surprise. +5683-32879-0008 2.95 You have been so ill, my poor Rachel. 5683-32879-0009 5.135 Ill and troubled, dear - troubled in mind, and miserably nervous. +5683-32866-0006 4.215 Yes, so they said; but that would, I think, have been worse. 5683-32879-0010 7.75 Poor Rachel! her nature recoiled from deceit, and she told, at all events, as much of the truth as she dared. +5683-32865-0014 2.615 He's not a man for country quarters! 5683-32879-0011 9.21 She spoke with a sudden energy, which partook of fear and passion, and flushed her thin cheek, and made her languid eyes flash. +5683-32865-0015 4.145 I had a horrid dream about him last night.' That? 5683-32879-0012 4.38 Thank you, Rachel, my Cousin Rachel, my only friend. +5683-32879-0001 3.66 Well, she was better, though she had had a bad night. 5683-32879-0014 8.405 Yes, something - everything,' said Rachel, hurriedly, looking frowningly at a flower which she was twirling in her fingers. +5683-32866-0023 2.745 All the furniture belonged to other times. 5683-32879-0018 7.44 It is an antipathy - an antipathy I cannot get over, dear Dorcas; you may think it a madness, but don't blame me. +5683-32866-0007 4.12 If a fellow's been a little bit wild, he's Beelzebub at once. 5683-32879-0019 6.35 I have very few to love me now, and I thought you might love me, as I have begun to love you. +5683-32865-0014 2.615 He's not a man for country quarters! 5683-32879-0020 6.545 And she threw her arms round her cousin's neck, and brave Rachel at last burst into tears. +5683-32865-0006 3.35 At dinner Lake was easy and amusing. 5683-32879-0021 4.09 Dorcas, in her strange way, was moved. +5683-32865-0003 3.51 They are cousins, you know; we are all cousins. 5683-32879-0022 4.175 I like you still, Rachel; I'm sure I'll always like you. +5683-32866-0001 3.47 And he added something still less complimentary. 5683-32879-0023 4.975 You resemble me, Rachel: you are fearless and inflexible and generous. +1580-141084-0003 4.1 No names, please"! said Holmes, as we knocked at Gilchrist's door. 1580-141084-0000 4.615 It was the Indian, whose dark silhouette appeared suddenly upon his blind. +1580-141084-0034 4.49 Well, well, don't trouble to answer. Listen, and see that I do you no injustice. 1580-141084-0002 5.905 This set of rooms is quite the oldest in the college, and it is not unusual for visitors to go over them. +1580-141083-0041 3.575 Let us hear the suspicions. I will look after the proofs". 1580-141084-0003 4.1 No names, please"! said Holmes, as we knocked at Gilchrist's door. +1580-141083-0050 3.085 I really don't think he knew much about it, mister Holmes. 1580-141084-0004 9.005 Of course, he did not realize that it was I who was knocking, but none the less his conduct was very uncourteous, and, indeed, under the circumstances rather suspicious". +1580-141083-0046 3.53 But I have occasionally done the same thing at other times". 1580-141084-0008 6.795 I cannot allow the examination to be held if one of the papers has been tampered with. The situation must be faced". +1580-141083-0021 3.715 There is no opening except the one pane," said our learned guide. 1580-141084-0009 4.685 It is possible that I may be in a position then to indicate some course of action. +1580-141084-0037 2.965 When I approached your room, I examined the window. 1580-141084-0011 5 When we were out in the darkness of the quadrangle, we again looked up at the windows. +1580-141084-0045 3.625 Suddenly he heard him at the very door. There was no possible escape. 1580-141084-0016 5.96 My friend did not appear to be depressed by his failure, but shrugged his shoulders in half humorous resignation. +1580-141083-0016 4.255 I was in such a hurry to come to you". "You left your door open"? 1580-141084-0021 4.01 On the palm were three little pyramids of black, doughy clay. +1580-141083-0030 3.48 mister Soames was somewhat overwhelmed by this flood of information. 1580-141084-0023 8.735 In a few hours the examination would commence, and he was still in the dilemma between making the facts public and allowing the culprit to compete for the valuable scholarship. +1580-141083-0046 3.53 But I have occasionally done the same thing at other times". 1580-141084-0024 9.185 He could hardly stand still so great was his mental agitation, and he ran towards Holmes with two eager hands outstretched. "Thank heaven that you have come! +1580-141083-0025 3.905 The man entered and took the papers, sheet by sheet, from the central table. 1580-141084-0026 6.995 If this matter is not to become public, we must give ourselves certain powers and resolve ourselves into a small private court martial. +1580-141083-0046 3.53 But I have occasionally done the same thing at other times". 1580-141084-0029 8.075 His troubled blue eyes glanced at each of us, and finally rested with an expression of blank dismay upon Bannister in the farther corner. +1580-141083-0050 3.085 I really don't think he knew much about it, mister Holmes. 1580-141084-0031 6.47 We want to know, mister Gilchrist, how you, an honourable man, ever came to commit such an action as that of yesterday"? +1580-141083-0028 2.585 Then he tossed it down and seized the next. 1580-141084-0032 4.995 For a moment Gilchrist, with upraised hand, tried to control his writhing features. +1580-141083-0040 3.75 One hardly likes to throw suspicion where there are no proofs". 1580-141084-0033 7 Come, come," said Holmes, kindly, "it is human to err, and at least no one can accuse you of being a callous criminal. +1580-141083-0036 3.98 Holmes held it out on his open palm in the glare of the electric light. 1580-141084-0034 4.49 Well, well, don't trouble to answer. Listen, and see that I do you no injustice. +1580-141084-0035 2.63 He could examine the papers in his own office. 1580-141084-0039 4.885 I entered, and I took you into my confidence as to the suggestions of the side table. +1580-141084-0035 2.63 He could examine the papers in his own office. 1580-141084-0040 5.985 He returned carrying his jumping shoes, which are provided, as you are aware, with several sharp spikes. +1580-141084-0045 3.625 Suddenly he heard him at the very door. There was no possible escape. 1580-141084-0041 7.99 No harm would have been done had it not been that, as he passed your door, he perceived the key which had been left by the carelessness of your servant. +1580-141083-0024 4.48 You left him in a chair, you say. Which chair"? "By the window there". 1580-141084-0042 5.06 A sudden impulse came over him to enter, and see if they were indeed the proofs. +1580-141083-0030 3.48 mister Soames was somewhat overwhelmed by this flood of information. 1580-141084-0047 5.25 I have a letter here, mister Soames, which I wrote to you early this morning in the middle of a restless night. +1580-141084-0045 3.625 Suddenly he heard him at the very door. There was no possible escape. 1580-141084-0048 9.265 It will be clear to you, from what I have said, that only you could have let this young man out, since you were left in the room, and must have locked the door when you went out. +1580-141083-0024 4.48 You left him in a chair, you say. Which chair"? "By the window there". 1580-141084-0049 7.575 It was simple enough, sir, if you only had known, but, with all your cleverness, it was impossible that you could know. +6930-76324-0010 2.69 What in the world is that"? queried Joyce. 6930-75918-0002 5.025 Congratulations were poured in upon the princess everywhere during her journey. +6930-76324-0013 4.305 It can't hurt anything, I'm sure, for we won't disturb things at all. 6930-75918-0006 5.85 This has indeed been a harassing day," continued the young man, his eyes fixed upon his friend. +6930-75918-0000 3.505 Concord returned to its place amidst the tents. 6930-75918-0008 4.785 Can you imagine why Buckingham has been so violent"? "I suspect". +6930-76324-0019 2.575 Now let's dust the furniture and pictures". 6930-75918-0009 7.28 It is you who are mistaken, Raoul; I have read his distress in his eyes, in his every gesture and action the whole day". +6930-75918-0000 3.505 Concord returned to its place amidst the tents. 6930-75918-0015 6.38 Thus it is that the honor of three is saved: our country's, our master's, and our own. +6930-76324-0013 4.305 It can't hurt anything, I'm sure, for we won't disturb things at all. 6930-75918-0017 6.16 But in this friendly pressure Raoul could detect the nervous agitation of a great internal conflict. +4077-13751-0019 2.92 Who began the quarrel? Was it the "Mormons"? 4077-13754-0000 4.78 The army found the people in poverty, and left them in comparative wealth. +4077-13751-0013 4.315 Their sufferings have never yet been fitly chronicled by human scribe. 4077-13754-0003 5.68 Moreover, had the people been inclined to rebellion what greater opportunity could they have wished? +4077-13754-0001 3.77 But a word further concerning the expedition in general. 4077-13754-0004 4.985 Already a North and a South were talked of - why not set up also a West? +4077-13751-0013 4.315 Their sufferings have never yet been fitly chronicled by human scribe. 4077-13754-0009 7.65 At the inception of plural marriage among the Latter day Saints, there was no law, national or state, against its practise. +1995-1837-0015 4.485 The squares of cotton, sharp edged, heavy, were just about to burst to bolls! 1995-1826-0000 9.485 In the debate between the senior societies her defence of the Fifteenth Amendment had been not only a notable bit of reasoning, but delivered with real enthusiasm. +1995-1837-0015 4.485 The squares of cotton, sharp edged, heavy, were just about to burst to bolls! 1995-1826-0002 4.605 John Taylor, who had supported her through college, was interested in cotton. +1995-1837-0000 3.865 He knew the Silver Fleece - his and Zora's - must be ruined. 1995-1826-0005 5.125 But, John, there's no society - just elementary work +1995-1837-0013 3.195 Then he looked down. The lagoon was dry. 1995-1826-0009 7.57 You ought to know, John, if I teach Negroes I'll scarcely see much of people in my own class". +1995-1837-0020 3.21 The years of the days of her dying were ten. 1995-1826-0011 8.94 Here she was teaching dirty children, and the smell of confused odors and bodily perspiration was to her at times unbearable. +1995-1836-0007 3.435 But you believe in some education"? asked Mary Taylor. 1995-1826-0012 6.18 She wanted a glance of the new books and periodicals and talk of great philanthropies and reforms. +1995-1837-0009 3.76 The lagoon had been level with the dykes a week ago; and now? 1995-1826-0013 8.77 So for the hundredth time she was thinking today, as she walked alone up the lane back of the barn, and then slowly down through the bottoms. +1995-1826-0015 3.55 She had almost forgotten that it was here within touch and sight. 1995-1826-0016 5.9 The glimmering sea of delicate leaves whispered and murmured before her, stretching away to the Northward. +1995-1837-0022 3.415 Up in the sick room Zora lay on the little white bed. 1995-1826-0017 6.145 There might be a bit of poetry here and there, but most of this place was such desperate prose. +1995-1837-0015 4.485 The squares of cotton, sharp edged, heavy, were just about to burst to bolls! 1995-1826-0018 5.01 Her regard shifted to the green stalks and leaves again, and she started to move away. +1995-1826-0004 3.035 Might learn something useful down there". 1995-1826-0019 5.25 Cotton is a wonderful thing, is it not, boys"? she said rather primly. +1995-1837-0011 3.375 He started at the thought. He hurried forth sadly. 1995-1826-0020 6.12 Miss Taylor did not know much about cotton, but at least one more remark seemed called for. +1995-1826-0003 3.09 Better go," he had counselled, sententiously. 1995-1826-0022 4.745 I suppose, though, it's too early for them". Then came the explosion. +1995-1837-0002 2.79 Ah! the swamp, the cruel swamp! 1995-1826-0024 5.095 The Golden Fleece - it's the Silver Fleece"! He harkened. +5683-32866-0001 3.47 And he added something still less complimentary. 5683-32865-0004 7.365 Whatever Lord Chelford said, Miss Brandon received it very graciously, and even with a momentary smile. +5683-32865-0002 2.78 He had his hand upon Lake's shoulder. 5683-32865-0007 6.065 I'm glad you like it,' says Wylder, chuckling benignantly on it, over his shoulder. +5683-32866-0001 3.47 And he added something still less complimentary. 5683-32865-0008 6.12 I believe I have a little taste that way; those are all real, you know, those jewels. +5683-32866-0000 2.645 Miss Lake declined the carriage to night. 5683-32865-0009 9.89 And he placed it in that gentleman's fingers, who now took his turn at the lamp, and contemplated the little parallelogram with a gleam of sly amusement. +5683-32866-0006 4.215 Yes, so they said; but that would, I think, have been worse. 5683-32865-0010 6.335 I was thinking it's very like the ace of hearts,' answered the captain softly, smiling on. +5683-32865-0003 3.51 They are cousins, you know; we are all cousins. 5683-32865-0011 6.355 Whereupon Lake laughed quietly, still looking on the ace of hearts with his sly eyes. +5683-32865-0015 4.145 I had a horrid dream about him last night.' That? 5683-32865-0013 7.095 Do you know?' 'Lake? Oh! I really can't tell; but he'll soon tire of country life. +5683-32879-0012 4.38 Thank you, Rachel, my Cousin Rachel, my only friend. 5683-32865-0015 4.145 I had a horrid dream about him last night.' That? +5683-32866-0006 4.215 Yes, so they said; but that would, I think, have been worse. 5683-32865-0017 5.455 All the time he was talking to me his angry little eyes were following Lake. +1320-122617-0005 4.4 The bear shook his shaggy sides, and then a well known voice replied: 1320-122617-0000 7.835 Notwithstanding the high resolution of Hawkeye he fully comprehended all the difficulties and danger he was about to incur. +1320-122617-0022 3.855 The Delawares are children of the tortoise, and they outstrip the deer". 1320-122617-0003 6.285 There was something in his air and manner that betrayed to the scout the utter confusion of the state of his mind. +1320-122617-0008 4.185 The young man is in bondage, and much I fear his death is decreed. 1320-122617-0005 4.4 The bear shook his shaggy sides, and then a well known voice replied: +1320-122612-0016 3.49 Run back, Uncas, and bring me the size of the singer's foot. 1320-122617-0006 5.655 Can these things be"? returned David, breathing more freely, as the truth began to dawn upon him. +1320-122617-0005 4.4 The bear shook his shaggy sides, and then a well known voice replied: 1320-122617-0008 4.185 The young man is in bondage, and much I fear his death is decreed. +1320-122612-0009 3.88 It would have been more wonderful had he spoken without a bidding. 1320-122617-0009 7.705 I greatly mourn that one so well disposed should die in his ignorance, and I have sought a goodly hymn-" "Can you lead me to him"? +1320-122617-0005 4.4 The bear shook his shaggy sides, and then a well known voice replied: 1320-122617-0010 10 The task will not be difficult," returned David, hesitating; "though I greatly fear your presence would rather increase than mitigate his unhappy fortunes". +1320-122617-0022 3.855 The Delawares are children of the tortoise, and they outstrip the deer". 1320-122617-0011 9.76 The lodge in which Uncas was confined was in the very center of the village, and in a situation, perhaps, more difficult than any other to approach, or leave, without observation. +1320-122617-0041 4.15 Uncas cast his skin, and stepped forth in his own beautiful proportions. 1320-122617-0012 7.59 Four or five of the latter only lingered about the door of the prison of Uncas, wary but close observers of the manner of their captive. +1320-122617-0022 3.855 The Delawares are children of the tortoise, and they outstrip the deer". 1320-122617-0014 4.9 They drew back a little from the entrance and motioned to the supposed conjurer to enter. +1320-122617-0005 4.4 The bear shook his shaggy sides, and then a well known voice replied: 1320-122617-0015 5.125 But the bear, instead of obeying, maintained the seat it had taken, and growled: +1320-122612-0016 3.49 Run back, Uncas, and bring me the size of the singer's foot. 1320-122617-0017 5.655 Then, as if satisfied of their safety, the scout left his position, and slowly entered the place. +1320-122617-0008 4.185 The young man is in bondage, and much I fear his death is decreed. 1320-122617-0018 9.695 It was silent and gloomy, being tenanted solely by the captive, and lighted by the dying embers of a fire, which had been used for the purposed of cookery. +1320-122617-0005 4.4 The bear shook his shaggy sides, and then a well known voice replied: 1320-122617-0019 8.23 Uncas occupied a distant corner, in a reclining attitude, being rigidly bound, both hands and feet, by strong and painful withes. +1320-122617-0041 4.15 Uncas cast his skin, and stepped forth in his own beautiful proportions. 1320-122617-0020 8.895 The scout, who had left David at the door, to ascertain they were not observed, thought it prudent to preserve his disguise until assured of their privacy. +1320-122617-0022 3.855 The Delawares are children of the tortoise, and they outstrip the deer". 1320-122617-0021 5.335 What shall we do with the Mingoes at the door? They count six, and this singer is as good as nothing". +1320-122617-0022 3.855 The Delawares are children of the tortoise, and they outstrip the deer". 1320-122617-0023 7.815 Uncas, who had already approached the door, in readiness to lead the way, now recoiled, and placed himself, once more, in the bottom of the lodge. +1320-122617-0022 3.855 The Delawares are children of the tortoise, and they outstrip the deer". 1320-122617-0024 7.555 But Hawkeye, who was too much occupied with his own thoughts to note the movement, continued speaking more to himself than to his companion. +1320-122617-0022 3.855 The Delawares are children of the tortoise, and they outstrip the deer". 1320-122617-0025 6.36 So, Uncas, you had better take the lead, while I will put on the skin again, and trust to cunning for want of speed". +1320-122617-0005 4.4 The bear shook his shaggy sides, and then a well known voice replied: 1320-122617-0026 5.225 Well, what can't be done by main courage, in war, must be done by circumvention. +1320-122617-0022 3.855 The Delawares are children of the tortoise, and they outstrip the deer". 1320-122617-0027 5.689938 As soon as these dispositions were made, the scout turned to David, and gave him his parting instructions. +1320-122617-0022 3.855 The Delawares are children of the tortoise, and they outstrip the deer". 1320-122617-0029 7.875 If you are not then knocked on the head, your being a non composser will protect you; and you'll then have a good reason to expect to die in your bed. +1320-122617-0008 4.185 The young man is in bondage, and much I fear his death is decreed. 1320-122617-0031 6.285 Bravely and generously has he battled in my behalf, and this, and more, will I dare in his service". +1320-122617-0022 3.855 The Delawares are children of the tortoise, and they outstrip the deer". 1320-122617-0034 9.485 Hold"! said David, perceiving that with this assurance they were about to leave him; "I am an unworthy and humble follower of one who taught not the damnable principle of revenge. +1320-122617-0022 3.855 The Delawares are children of the tortoise, and they outstrip the deer". 1320-122617-0037 7.18 The Delaware dog"! he said, leaning forward, and peering through the dim light to catch the expression of the other's features; "is he afraid? +1320-122617-0022 3.855 The Delawares are children of the tortoise, and they outstrip the deer". 1320-122617-0039 7.055 The Mohican started on his feet, and shook his shaggy covering, as though the animal he counterfeited was about to make some desperate effort. +1320-122617-0041 4.15 Uncas cast his skin, and stepped forth in his own beautiful proportions. 1320-122617-0040 7.975 He had no occasion to delay, for at the next instant a burst of cries filled the outer air, and ran along the whole extent of the village. +1320-122612-0016 3.49 Run back, Uncas, and bring me the size of the singer's foot. 1320-122617-0041 4.15 Uncas cast his skin, and stepped forth in his own beautiful proportions. +121-127105-0036 4.15 But was that all her reward"? one of the ladies asked. 121-121726-0000 8.46 Also, a popular contrivance whereby love making may be suspended but not stopped during the picnic season. +121-121726-0004 4.02 Heaven, a good place to be raised to. 121-121726-0001 5.925 Harangue The tiresome product of a tireless tongue. +121-121726-0013 2.49 Tied to a woman. 121-121726-0002 4.41 angor, pain. Painful to hear. +121-127105-0008 2.76 He hung fire again. "A woman's. 121-121726-0003 6.755 Hay fever, a heart trouble caused by falling in love with a grass widow. +121-121726-0006 3.895 Heredity, the cause of all our faults. 121-121726-0004 4.02 Heaven, a good place to be raised to. +121-127105-0008 2.76 He hung fire again. "A woman's. 121-121726-0007 6.73 Horse sense, a degree of wisdom that keeps one from betting on the races. +121-121726-0014 3.165 Hypocrite, a horse dealer. 121-121726-0008 4.99 Hose Man's excuse for wetting the walk. +121-121726-0006 3.895 Heredity, the cause of all our faults. 121-121726-0009 7.26 Hotel, a place where a guest often gives up good dollars for poor quarters. +121-127105-0008 2.76 He hung fire again. "A woman's. 121-121726-0010 9.81 Housecleaning, a domestic upheaval that makes it easy for the government to enlist all the soldiers it needs. +121-121726-0014 3.165 Hypocrite, a horse dealer. 121-121726-0011 4.035 Husband, the next thing to a wife. +121-121726-0002 4.41 angor, pain. Painful to hear. 121-121726-0012 4.045 hussy, woman, and bond, tie. +61-70970-0016 4.37 We will go out together to the bower; there is a way down to the court from my window. 61-70970-0000 6.075 Young Fitzooth had been commanded to his mother's chamber so soon as he had come out from his converse with the Squire. +61-70970-0012 3.135 Yet he will teach you a few tricks when morning is come. 61-70970-0001 6.155 There befell an anxious interview, Mistress Fitzooth arguing for and against the Squire's project in a breath. +61-70968-0045 3.475 Pray follow us, with mine and my lord Sheriff's men". 61-70970-0002 4.165 Most of all Robin thought of his father. What would he counsel? +61-70968-0056 3.565 The wine did certainly bring back the color to the Squire's cheeks. 61-70970-0007 4.485 He was in deep converse with the clerk, and entered the hall holding him by the arm. +61-70968-0039 3.805 And mine is Will Stuteley. Shall we be comrades"? 61-70970-0011 6.075 As any in England, I would say," said Gamewell, proudly. "That is, in his day. +61-70968-0016 3.72 And then they became vexed, and would have snatched your purse from us. 61-70970-0013 4.35 There was no chance to alter his sleeping room to one nearer to Gamewell's chamber. +61-70968-0046 3.55 Nottingham Castle was reached, and admittance was demanded. 61-70970-0015 8.415 Will," cried he, softly; and Stuteley, who had chosen his couch across the door of his young master's chamber, sprang up at once in answer. +61-70968-0029 3.495 The Squire helped to thrust them all in and entered swiftly himself. 61-70970-0016 4.37 We will go out together to the bower; there is a way down to the court from my window. +61-70968-0046 3.55 Nottingham Castle was reached, and admittance was demanded. 61-70970-0018 4.6 The hours passed wearily by, and movement could yet be heard about the hall. +61-70970-0009 3.405 Tis late; and I go myself within a short space. 61-70970-0020 5.025 Will," whispered Robin, opening his door as he spoke, "are you ready"? +61-70970-0013 4.35 There was no chance to alter his sleeping room to one nearer to Gamewell's chamber. 61-70970-0021 5.405 They then renewed their journey, and, under the better light, made a safe crossing of the stable roofs. +61-70970-0016 4.37 We will go out together to the bower; there is a way down to the court from my window. 61-70970-0024 7.235 They moved thereafter cautiously about the hut, groping before and about them to find something to show that Warrenton had fulfilled his mission. +61-70970-0016 4.37 We will go out together to the bower; there is a way down to the court from my window. 61-70970-0025 7.435 They were upon the verge of an open trap, in the far corner of the hut; and Stuteley had tripped over the edge of the reversed flap mouth of this pit. +61-70970-0033 3.42 Truly such a horse should be worth much in Nottingham Fair! 61-70970-0026 5.475 Fitzooth's hand rested at last upon the top rung of a ladder, and slowly the truth came to him. +61-70970-0032 3.135 enquired Robin, with his suspicions still upon him. 61-70970-0027 5.08 Robin carefully descended the ladder and found himself soon upon firm rocky ground. +61-70970-0016 4.37 We will go out together to the bower; there is a way down to the court from my window. 61-70970-0028 6.55 Stuteley was by his side in a flash: and then they both began feeling about them to ascertain the shape and character of this vault. +61-70968-0055 3.965 Robin was glad when, at length, they were left to their own devices. 61-70970-0029 4.03 From the blackness behind the light they heard a voice - Warrenton's! +61-70970-0007 4.485 He was in deep converse with the clerk, and entered the hall holding him by the arm. 61-70970-0031 5.135 cried he, waving the lanthorn before him to make sure that these were no ghosts in front of him. +61-70968-0039 3.805 And mine is Will Stuteley. Shall we be comrades"? 61-70970-0034 4.485 Nay, nay, lording," answered Warrenton, with a half laugh. +61-70968-0006 2.935 But then the picture was gone as quickly as it came". 61-70970-0035 7.405 Warrenton spoke thus with significance, to show Robin that he was not to think Geoffrey's claims to the estate would be passed by. +61-70970-0033 3.42 Truly such a horse should be worth much in Nottingham Fair! 61-70970-0036 6.785 Robin Fitzooth saw that his doubts of Warrenton had been unfair: and he became ashamed of himself for harboring them. +61-70968-0052 2.65 But who is this fellow plucking at your sleeve? 61-70970-0037 5.98 His tones rang pleasantly on Warrenton's ears, and forthwith a good fellowship was heralded between them. +61-70968-0046 3.55 Nottingham Castle was reached, and admittance was demanded. 61-70970-0039 6.665 He implores us to be discreet as the grave in this matter, for in sooth his life is in the hollow of our hands". +61-70970-0016 4.37 We will go out together to the bower; there is a way down to the court from my window. 61-70970-0040 4.165 They regained their apartment, apparently without disturbing the household of Gamewell. +5105-28240-0002 4.01 exclaimed Servadac, keeping his eye unmoved at his telescope. 5105-28240-0000 5.455 Fast as his legs could carry him, Servadac had made his way to the top of the cliff. +5105-28240-0016 4.17 To all these inquiries, the count responded in the affirmative. 5105-28240-0002 4.01 exclaimed Servadac, keeping his eye unmoved at his telescope. +5105-28240-0013 2.96 Nothing more than you know yourself". 5105-28240-0003 5.515 She is under sail; but she is Count Timascheff's yacht". He was right. +5105-28240-0014 3.07 Are you certain that this is the Mediterranean"? 5105-28240-0004 6.015 If the count were on board, a strange fatality was bringing him to the presence of his rival. +5105-28240-0014 3.07 Are you certain that this is the Mediterranean"? 5105-28240-0005 7.4 He reckoned, therefore, not only upon ascertaining the extent of the late catastrophe, but upon learning its cause. +5105-28240-0014 3.07 Are you certain that this is the Mediterranean"? 5105-28240-0007 4.625 Servadac took it for granted that the Dobryna was endeavoring to put in. +5105-28241-0014 2.995 Another circumstance was most remarkable. 5105-28240-0011 6.02 I left you on a continent, and here I have the honor of finding you on an island". +5105-28240-0014 3.07 Are you certain that this is the Mediterranean"? 5105-28240-0015 8.525 For some moments he seemed perfectly stupefied; then, recovering himself, he began to overwhelm the count with a torrent of questions. +5105-28240-0002 4.01 exclaimed Servadac, keeping his eye unmoved at his telescope. 5105-28240-0016 4.17 To all these inquiries, the count responded in the affirmative. +5105-28241-0014 2.995 Another circumstance was most remarkable. 5105-28240-0017 5.665 Some mysterious force seemed to have brought about a convulsion of the elements. +5105-28241-0003 3.98 Steam up and canvas spread, the schooner started eastwards. 5105-28240-0019 6.240062 My yacht is at your service, sir, even should you require to make a tour round the world". +5105-28233-0001 4.49 He seemed born to please without being conscious of the power he possessed. 5105-28240-0022 4.725 It was on the last day of January that the repairs of the schooner were completed. +5105-28241-0003 3.98 Steam up and canvas spread, the schooner started eastwards. 5105-28240-0024 8.2 Doubts now arose, and some discussion followed, whether or not it was desirable for Ben Zoof to accompany his master. +1284-1181-0002 3.835 The head of the Patchwork Girl was the most curious part of her. 1284-1181-0003 4.505 The hair was of brown yarn and hung down on her neck in several neat braids. +1284-1180-0027 3.27 Yet that task was not so easy as you may suppose. 1284-1181-0004 7.15 Gold is the most common metal in the Land of Oz and is used for many purposes because it is soft and pliable. +1284-1180-0027 3.27 Yet that task was not so easy as you may suppose. 1284-1181-0007 4.04 She poured into the dish a quantity from each of these bottles. +1284-1180-0027 3.27 Yet that task was not so easy as you may suppose. 1284-1181-0008 6.08 I think that will do," she continued, "for the other qualities are not needed in a servant". +1284-1181-0002 3.835 The head of the Patchwork Girl was the most curious part of her. 1284-1181-0009 5.245 She ran to her husband's side at once and helped him lift the four kettles from the fire. +1284-1181-0002 3.835 The head of the Patchwork Girl was the most curious part of her. 1284-1181-0010 6.435 Their contents had all boiled away, leaving in the bottom of each kettle a few grains of fine white powder. +1284-1181-0002 3.835 The head of the Patchwork Girl was the most curious part of her. 1284-1181-0011 7.75 Very carefully the Magician removed this powder, placing it all together in a golden dish, where he mixed it with a golden spoon. +1284-1180-0004 4.285 When they were outside, Unc simply latched the door and started up the path. 1284-1181-0012 8.51 No one saw him do this, for all were looking at the Powder of Life; but soon the woman remembered what she had been doing, and came back to the cupboard. +1284-1181-0002 3.835 The head of the Patchwork Girl was the most curious part of her. 1284-1181-0014 7.92 He selected a small gold bottle with a pepper box top, so that the powder might be sprinkled on any object through the small holes. +1284-1181-0007 4.04 She poured into the dish a quantity from each of these bottles. 1284-1181-0015 5.115 Most people talk too much, so it is a relief to find one who talks too little". +1284-1181-0007 4.04 She poured into the dish a quantity from each of these bottles. 1284-1181-0016 9.515 I am not allowed to perform magic, except for my own amusement," he told his visitors, as he lighted a pipe with a crooked stem and began to smoke. +1284-1181-0002 3.835 The head of the Patchwork Girl was the most curious part of her. 1284-1181-0020 6.73 Dear me; what a chatterbox you're getting to be, Unc," remarked the Magician, who was pleased with the compliment. +4446-2271-0012 3.78 I say, Sir Harry, the little girl's going famously to night, isn't she"? 4446-2275-0000 6.34 The stop at Queenstown, the tedious passage up the Mersey, were things that he noted dimly through his growing impatience. +4446-2273-0002 3.295 Lamb wouldn't care a great deal about many of them, I fancy". 4446-2275-0001 4.66 She blushed and smiled and fumbled his card in her confusion before she ran upstairs. +4446-2271-0005 3.395 She saves her hand, too. She's at her best in the second act. 4446-2275-0002 7.675 Alexander paced up and down the hallway, buttoning and unbuttoning his overcoat, until she returned and took him up to Hilda's living room. +4446-2271-0006 2.905 He's been wanting to marry Hilda these three years and more. 4446-2275-0005 4.445 I felt it in my bones when I woke this morning that something splendid was going to turn up. +4446-2273-0005 4.125 I haven't had a chance yet to tell you what a jolly little place I think this is. 4446-2275-0007 8.975 She pushed him toward the big chair by the fire, and sat down on a stool at the opposite side of the hearth, her knees drawn up to her chin, laughing like a happy little girl. +4446-2271-0003 3.7 It's been on only two weeks, and I've been half a dozen times already. 4446-2275-0008 4.13 When did you come, Bartley, and how did it happen? You haven't spoken a word". +4446-2275-0035 4.075 Alexander rose and shook himself angrily. "Yes, I know I'm cowardly. 4446-2275-0012 6.025 She looked at his heavy shoulders and big, determined head, thrust forward like a catapult in leash. +4446-2273-0036 3.12 Alexander unclenched the two hands at his sides. 4446-2275-0016 7.3 Hilda watched him from her corner, trembling and scarcely breathing, dark shadows growing about her eyes. "It... +4446-2275-0015 2.98 He pulled up a window as if the air were heavy. 4446-2275-0019 4.93 The world is all there, just as it used to be, but I can't get at it any more. +4446-2273-0033 3.3 For a long time neither Hilda nor Bartley spoke. 4446-2275-0021 5.05 Hilda's face quivered, but she whispered: "Yes, I think it must have been. +4446-2273-0030 2.885 Alexander went over and opened the window for her. 4446-2275-0026 5.495 She closed her eyes and took a deep breath, as if to draw in again the fragrance of those days. +4446-2275-0035 4.075 Alexander rose and shook himself angrily. "Yes, I know I'm cowardly. 4446-2275-0029 6.28 Please tell me one thing, Bartley. At least, tell me that you believe I thought I was making you happy". +4446-2275-0010 3.735 Alexander leaned forward and warmed his hands before the blaze. 4446-2275-0033 7.06 What I mean is that I want you to promise never to see me again, no matter how often I come, no matter how hard I beg". +4446-2271-0011 3.945 Sir Harry Towne, mister Bartley Alexander, the American engineer". 4446-2275-0035 4.075 Alexander rose and shook himself angrily. "Yes, I know I'm cowardly. +4446-2273-0017 2.74 How jolly it was being young, Hilda! 4446-2275-0038 4.53 I will ask the least imaginable, but I must have something! +4446-2271-0005 3.395 She saves her hand, too. She's at her best in the second act. 4446-2275-0040 6.965 The sight of you, Bartley, to see you living and happy and successful can I never make you understand what that means to me"? +4446-2271-0005 3.395 She saves her hand, too. She's at her best in the second act. 4446-2275-0041 4.755 You see, loving some one as I love you makes the whole world different. +4446-2275-0011 2.435 Bartley bent lower over the fire. 4446-2275-0042 5.4 And then you came back, not caring very much, but it made no difference". +4446-2273-0033 3.3 For a long time neither Hilda nor Bartley spoke. 4446-2275-0043 5.88 Bartley bent over and took her in his arms, kissing her mouth and her wet, tired eyes. +5142-33396-0015 4.31 As our boat flashed down the rollers into the water I made this song and sang it: 5142-36377-0001 5.39 In five minutes I was in a new world, and my melancholy room was full of the liveliest French company. +5142-33396-0062 2.9 Now she put her hand on his arm and smiled and said: 5142-36377-0002 5.62 The sound of an imperative and uncompromising bell recalled me in due time to the regions of reality. +5142-33396-0050 2.885 May you drink heart's ease from it for many years. 5142-36377-0004 5.485 She signed to me, with a ghostly solemnity, to take the vacant place on the left of her father. +5142-33396-0023 3.48 It was so dark that I could see nothing but a few sparks on the hearth. 5142-36377-0005 7.085 The door opened again while I was still studying the two brothers, without, I honestly confess, being very favorably impressed by either of them. +5142-33396-0049 3.305 Here, friend, take it,' and he thrust it into the farmer's hand. 5142-36377-0006 4.635 A new member of the family circle, who instantly attracted my attention, entered the room. +5142-33396-0053 3.93 I took five great bracelets of gold from our treasure chest and gave them to him. 5142-36377-0007 6.18 A little cracked" - that in the popular phrase was my impression of the stranger who now made his appearance in the supper room. +5142-36586-0000 3.65 It is manifest that man is now subject to much variability. 5142-36377-0010 4.294937 He is not well; he has come over the ocean for rest, and change of scene. +5142-33396-0023 3.48 It was so dark that I could see nothing but a few sparks on the hearth. 5142-36377-0013 6.585 They pointedly drew back from John Jago as he approached the empty chair next to me and moved round to the opposite side of the table. +5142-33396-0049 3.305 Here, friend, take it,' and he thrust it into the farmer's hand. 5142-36377-0015 4.34 Our first impressions of people are, in nine cases out of ten, the right impressions. +5142-33396-0049 3.305 Here, friend, take it,' and he thrust it into the farmer's hand. 5142-36377-0017 4.685 The only cheerful conversation was the conversation across the table between Naomi and me. +5142-33396-0002 3.67 Two hundred warriors feasted in his hall and followed him to battle. 5142-36377-0018 4.97 He looked up at Naomi doubtingly from his plate, and looked down again slowly with a frown. +5142-33396-0011 3.52 There she sat on the rollers, as fair a ship as I ever saw. 5142-36377-0020 4.53 A more dreary and more disunited family party I never sat at the table with. +5142-36586-0000 3.65 It is manifest that man is now subject to much variability. 5142-36377-0023 5.79 You were quite right to say 'No,'" Ambrose began. "Never smoke with John Jago. His cigars will poison you". +5142-33396-0040 2.81 And these shall follow your thralls in the same way. 5142-36377-0024 5.78 Naomi shook her forefinger reproachfully at them, as if the two sturdy young farmers had been two children. +8555-292519-0015 2.85 He had broken into her courtyard. 8555-292519-0005 9.575 While the old gold and the marble stays, Forever gleaming its soft strong blaze, Calm in the early evening glow. +8555-292519-0013 4.185 That was but rustling of dripping plants in the dark. 8555-292519-0007 8.405 It is my heart hung in the sky; And no clouds ever float between The grave flowers and my heart on high. +8555-292519-0015 2.85 He had broken into her courtyard. 8555-292519-0008 6.025 Over the track lined city street The young men, the grinning men, pass. +8555-284449-0009 3.27 You are, mate," replied the sailor. 8555-292519-0010 5.77 Old dances are simplified of their yearning, bleached by Time. +8555-292519-0015 2.85 He had broken into her courtyard. 8555-292519-0012 5.17 Through the black night rain, he sang to her window bars: +8555-292519-0015 2.85 He had broken into her courtyard. 8555-292519-0013 4.185 That was but rustling of dripping plants in the dark. +5683-32865-0001 2.58 said Lord Chelford, addressing me. 5683-32866-0002 5.125 But don't these very wise things sometimes turn out very foolishly? +5683-32865-0001 2.58 said Lord Chelford, addressing me. 5683-32866-0004 9.225 By this time Lord Chelford and Wylder returned; and, disgusted rather with myself, I ruminated on my want of general ship. +5683-32866-0014 3.97 Don't insult me, Stanley, by talking again as you did this morning. 5683-32866-0005 4.59 and he made a little dip of his cane towards Brandon Hall, over his shoulder. +5683-32866-0008 3.3 Bracton's a very good fellow, I can assure you. 5683-32866-0006 4.215 Yes, so they said; but that would, I think, have been worse. +5683-32879-0001 3.66 Well, she was better, though she had had a bad night. 5683-32866-0007 4.12 If a fellow's been a little bit wild, he's Beelzebub at once. +5683-32866-0015 2.83 What I say is altogether on your own account. 5683-32866-0011 7.37 Their walk continued silent for the greater part, neither was quite satisfied with the other. But Rachel at last said +5683-32866-0015 2.83 What I say is altogether on your own account. 5683-32866-0012 8.26 Now that's impossible, Radie; for I really don't think I once thought of him all this evening - except just while we were talking. +5683-32866-0014 3.97 Don't insult me, Stanley, by talking again as you did this morning. 5683-32866-0013 9.93 There was a bright moonlight, broken by the shadows of overhanging boughs and withered leaves; and the mottled lights and shadows glided oddly across his pale features. +5683-32866-0006 4.215 Yes, so they said; but that would, I think, have been worse. 5683-32866-0016 4.88 Mark my words, you'll find him too strong for you; aye, and too deep. +5683-32865-0001 2.58 said Lord Chelford, addressing me. 5683-32866-0017 4.585 I am very uneasy about it, whatever it is. I can't help it. +5683-32879-0001 3.66 Well, she was better, though she had had a bad night. 5683-32866-0018 5.455 To my mind there has always been something inexpressibly awful in family feuds. +5683-32866-0001 3.47 And he added something still less complimentary. 5683-32866-0021 7.9 My bed was unexceptionably comfortable, but, in my then mood, I could have wished it a great deal more modern. +5683-32866-0014 3.97 Don't insult me, Stanley, by talking again as you did this morning. 5683-32866-0024 9.855 I shan't trouble you about my train of thoughts or fancies; but I began to feel very like a gentleman in a ghost story, watching experimentally in a haunted chamber. +5683-32866-0008 3.3 Bracton's a very good fellow, I can assure you. 5683-32866-0027 4.755 A cold, bright moon was shining with clear sharp lights and shadows. +5683-32879-0012 4.38 Thank you, Rachel, my Cousin Rachel, my only friend. 5683-32866-0028 5.62 The sombre old trees, like gigantic hearse plumes, black and awful. +5683-32866-0003 2.865 In the meantime I had formed a new idea of her. 5683-32866-0030 4.845 A little bit of plaster tumbled down the chimney, and startled me confoundedly. +8555-284447-0020 4.09 The goat's warlike spirit was roused by this successful attack. 8555-284449-0001 8.63 Then they all marched out a little way into the fields and found that the Army of Pinkies had already formed and was advancing steadily toward them. +8555-284447-0020 4.09 The goat's warlike spirit was roused by this successful attack. 8555-284449-0003 8.875 When the Blueskins saw Ghip Ghisizzle they raised another great shout, for he was the favorite of the soldiers and very popular with all the people. +8555-284447-0003 4.415 But Captain Bill made no such attempt, knowing it would be useless. 8555-284449-0007 9.31 Now, then, let's enter the City and enjoy the grand feast that's being cooked. I'm nearly starved, myself, for this conquering kingdoms is hard work". +8555-284447-0020 4.09 The goat's warlike spirit was roused by this successful attack. 8555-284449-0008 6.135 Then she gave Rosalie back her magic ring, thanking the kind Witch for all she had done for them. +8555-284447-0020 4.09 The goat's warlike spirit was roused by this successful attack. 8555-284449-0012 9.87 I'll gladly do that," promised the new Boolooroo; "and I'll feed the honorable goat all the shavings and leather and tin cans he can eat, besides the grass. +8555-284447-0003 4.415 But Captain Bill made no such attempt, knowing it would be useless. 8555-284449-0013 5.775 Scuse me," said Trot; "I neglected to tell you that you're not the Boolooroo any more. +8555-292519-0013 4.185 That was but rustling of dripping plants in the dark. 8555-284449-0015 5.12 I'll not be wicked any more," sighed the old Boolooroo; "I'll reform. +8555-284447-0022 3.56 I had a notion it was you, mate, as saved me from the knife. 8555-284449-0016 5.895 As a private citizen I shall be a model of deportment, because it would be dangerous to be otherwise". +8555-284447-0020 4.09 The goat's warlike spirit was roused by this successful attack. 8555-284449-0018 7.03 So Ghip Ghisizzle ordered the Captain to take a file of soldiers and escort the raving beauties to their new home. +8555-284447-0020 4.09 The goat's warlike spirit was roused by this successful attack. 8555-284449-0019 7.61 That evening Trot gave a grand ball in the palace, to which the most important of the Pinkies and the Blueskins were invited. +8555-284447-0020 4.09 The goat's warlike spirit was roused by this successful attack. 8555-284449-0020 5.095 The combined bands of both the countries played the music and a fine supper was served. diff --git a/src/f5_tts/pyproject.toml b/src/f5_tts/pyproject.toml new file mode 100644 index 0000000000000000000000000000000000000000..f6203209f475983fb6895ab3e832a6fe7dc02edd --- /dev/null +++ b/src/f5_tts/pyproject.toml @@ -0,0 +1,64 @@ +[build-system] +requires = ["setuptools >= 61.0", "setuptools-scm>=8.0"] +build-backend = "setuptools.build_meta" + +[project] +name = "f5-tts" +version = "1.1.7" +description = "F5-TTS: A Fairytaler that Fakes Fluent and Faithful Speech with Flow Matching" +readme = "README.md" +license = {text = "MIT License"} +classifiers = [ + "License :: OSI Approved :: MIT License", + "Operating System :: OS Independent", + "Programming Language :: Python :: 3", +] +dependencies = [ + "accelerate>=0.33.0", + "bitsandbytes>0.37.0; platform_machine != 'arm64' and platform_system != 'Darwin'", + "cached_path", + "click", + "datasets", + "ema_pytorch>=0.5.2", + "gradio>=3.45.2", + "hydra-core>=1.3.0", + "jieba", + "librosa", + "matplotlib", + "numpy<=1.26.4", + "pydantic<=2.10.6", + "pydub", + "pypinyin", + "safetensors", + "soundfile", + "tomli", + "torch>=2.0.0", + "torchaudio>=2.0.0", + "torchdiffeq", + "tqdm>=4.65.0", + "transformers", + "transformers_stream_generator", + "unidecode", + "vocos", + "wandb", + "x_transformers>=1.31.14", +] + +[project.optional-dependencies] +eval = [ + "faster_whisper==0.10.1", + "funasr", + "jiwer", + "modelscope", + "zhconv", + "zhon", +] + +[project.urls] +Homepage = "https://github.com/SWivid/F5-TTS" + +[project.scripts] +"f5-tts_infer-cli" = "f5_tts.infer.infer_cli:main" +"f5-tts_infer-gradio" = "f5_tts.infer.infer_gradio:main" +"f5-tts_finetune-cli" = "f5_tts.train.finetune_cli:main" +"f5-tts_finetune-gradio" = "f5_tts.train.finetune_gradio:main" diff --git a/src/f5_tts/ruff.toml b/src/f5_tts/ruff.toml new file mode 100644 index 0000000000000000000000000000000000000000..c6ba83a8cb10c4c056decb99630d7bd07d387f4f --- /dev/null +++ b/src/f5_tts/ruff.toml @@ -0,0 +1,10 @@ +line-length = 120 +target-version = "py310" + +[lint] +# Only ignore variables with names starting with "_". +dummy-variable-rgx = "^_.*$" + +[lint.isort] +force-single-line = false +lines-after-imports = 2 diff --git a/src/f5_tts/src/f5_tts/api.py b/src/f5_tts/src/f5_tts/api.py new file mode 100644 index 0000000000000000000000000000000000000000..170c61c9154e78bf3de5b34dacf7143fa8be9921 --- /dev/null +++ b/src/f5_tts/src/f5_tts/api.py @@ -0,0 +1,164 @@ +import random +import sys +from importlib.resources import files + +import soundfile as sf +import tqdm +from cached_path import cached_path +from hydra.utils import get_class +from omegaconf import OmegaConf + +from f5_tts.infer.utils_infer import ( + infer_process, + load_model, + load_vocoder, + preprocess_ref_audio_text, + remove_silence_for_generated_wav, + save_spectrogram, + transcribe, +) +from f5_tts.model.utils import seed_everything + + +class F5TTS: + def __init__( + self, + model="F5TTS_v1_Base", + ckpt_file="", + vocab_file="", + ode_method="euler", + use_ema=True, + vocoder_local_path=None, + device=None, + hf_cache_dir=None, + ): + model_cfg = OmegaConf.load(str(files("f5_tts").joinpath(f"configs/{model}.yaml"))) + model_cls = get_class(f"f5_tts.model.{model_cfg.model.backbone}") + model_arc = model_cfg.model.arch + + self.mel_spec_type = model_cfg.model.mel_spec.mel_spec_type + self.target_sample_rate = model_cfg.model.mel_spec.target_sample_rate + + self.ode_method = ode_method + self.use_ema = use_ema + + if device is not None: + self.device = device + else: + import torch + + self.device = ( + "cuda" + if torch.cuda.is_available() + else "xpu" + if torch.xpu.is_available() + else "mps" + if torch.backends.mps.is_available() + else "cpu" + ) + + # Load models + self.vocoder = load_vocoder( + self.mel_spec_type, vocoder_local_path is not None, vocoder_local_path, self.device, hf_cache_dir + ) + + repo_name, ckpt_step, ckpt_type = "F5-TTS", 1250000, "safetensors" + + # override for previous models + if model == "F5TTS_Base": + if self.mel_spec_type == "vocos": + ckpt_step = 1200000 + elif self.mel_spec_type == "bigvgan": + model = "F5TTS_Base_bigvgan" + ckpt_type = "pt" + elif model == "E2TTS_Base": + repo_name = "E2-TTS" + ckpt_step = 1200000 + + if not ckpt_file: + ckpt_file = str( + cached_path(f"hf://SWivid/{repo_name}/{model}/model_{ckpt_step}.{ckpt_type}", cache_dir=hf_cache_dir) + ) + self.ema_model = load_model( + model_cls, model_arc, ckpt_file, self.mel_spec_type, vocab_file, self.ode_method, self.use_ema, self.device + ) + + def transcribe(self, ref_audio, language=None): + return transcribe(ref_audio, language) + + def export_wav(self, wav, file_wave, remove_silence=False): + sf.write(file_wave, wav, self.target_sample_rate) + + if remove_silence: + remove_silence_for_generated_wav(file_wave) + + def export_spectrogram(self, spec, file_spec): + save_spectrogram(spec, file_spec) + + def infer( + self, + ref_file, + ref_text, + gen_text, + show_info=print, + progress=tqdm, + target_rms=0.1, + cross_fade_duration=0.15, + sway_sampling_coef=-1, + cfg_strength=2, + nfe_step=32, + speed=1.0, + fix_duration=None, + remove_silence=False, + file_wave=None, + file_spec=None, + seed=None, + ): + if seed is None: + seed = random.randint(0, sys.maxsize) + seed_everything(seed) + self.seed = seed + + ref_file, ref_text = preprocess_ref_audio_text(ref_file, ref_text) + + wav, sr, spec = infer_process( + ref_file, + ref_text, + gen_text, + self.ema_model, + self.vocoder, + self.mel_spec_type, + show_info=show_info, + progress=progress, + target_rms=target_rms, + cross_fade_duration=cross_fade_duration, + nfe_step=nfe_step, + cfg_strength=cfg_strength, + sway_sampling_coef=sway_sampling_coef, + speed=speed, + fix_duration=fix_duration, + device=self.device, + ) + + if file_wave is not None: + self.export_wav(wav, file_wave, remove_silence) + + if file_spec is not None: + self.export_spectrogram(spec, file_spec) + + return wav, sr, spec + + +if __name__ == "__main__": + f5tts = F5TTS() + + wav, sr, spec = f5tts.infer( + ref_file=str(files("f5_tts").joinpath("infer/examples/basic/basic_ref_en.wav")), + ref_text="some call me nature, others call me mother nature.", + gen_text="""I don't really care what you call me. I've been a silent spectator, watching species evolve, empires rise and fall. But always remember, I am mighty and enduring. Respect me and I'll nurture you; ignore me and you shall face the consequences.""", + file_wave=str(files("f5_tts").joinpath("../../tests/api_out.wav")), + file_spec=str(files("f5_tts").joinpath("../../tests/api_out.png")), + seed=None, + ) + + print("seed :", f5tts.seed) diff --git a/src/f5_tts/src/f5_tts/configs/E2TTS_Base.yaml b/src/f5_tts/src/f5_tts/configs/E2TTS_Base.yaml new file mode 100644 index 0000000000000000000000000000000000000000..4c64c18ca85c2e3730d2b2a79c0f533f5ceb141d --- /dev/null +++ b/src/f5_tts/src/f5_tts/configs/E2TTS_Base.yaml @@ -0,0 +1,49 @@ +hydra: + run: + dir: ckpts/${model.name}_${model.mel_spec.mel_spec_type}_${model.tokenizer}_${datasets.name}/${now:%Y-%m-%d}/${now:%H-%M-%S} + +datasets: + name: Emilia_ZH_EN # dataset name + batch_size_per_gpu: 38400 # 8 GPUs, 8 * 38400 = 307200 + batch_size_type: frame # frame | sample + max_samples: 64 # max sequences per batch if use frame-wise batch_size. we set 32 for small models, 64 for base models + num_workers: 16 + +optim: + epochs: 11 + learning_rate: 7.5e-5 + num_warmup_updates: 20000 # warmup updates + grad_accumulation_steps: 1 # note: updates = steps / grad_accumulation_steps + max_grad_norm: 1.0 # gradient clipping + bnb_optimizer: False # use bnb 8bit AdamW optimizer or not + +model: + name: E2TTS_Base + tokenizer: pinyin + tokenizer_path: null # if 'custom' tokenizer, define the path want to use (should be vocab.txt) + backbone: UNetT + arch: + dim: 1024 + depth: 24 + heads: 16 + ff_mult: 4 + text_mask_padding: False + pe_attn_head: 1 + mel_spec: + target_sample_rate: 24000 + n_mel_channels: 100 + hop_length: 256 + win_length: 1024 + n_fft: 1024 + mel_spec_type: vocos # vocos | bigvgan + vocoder: + is_local: False # use local offline ckpt or not + local_path: null # local vocoder path + +ckpts: + logger: wandb # wandb | tensorboard | null + log_samples: True # infer random sample per save checkpoint. wip, normal to fail with extra long samples + save_per_updates: 50000 # save checkpoint per updates + keep_last_n_checkpoints: -1 # -1 to keep all, 0 to not save intermediate, > 0 to keep last N checkpoints + last_per_updates: 5000 # save last checkpoint per updates + save_dir: ckpts/${model.name}_${model.mel_spec.mel_spec_type}_${model.tokenizer}_${datasets.name} \ No newline at end of file diff --git a/src/f5_tts/src/f5_tts/configs/E2TTS_Small.yaml b/src/f5_tts/src/f5_tts/configs/E2TTS_Small.yaml new file mode 100644 index 0000000000000000000000000000000000000000..879eceace0799978f81105a25db6269f220ef8a9 --- /dev/null +++ b/src/f5_tts/src/f5_tts/configs/E2TTS_Small.yaml @@ -0,0 +1,49 @@ +hydra: + run: + dir: ckpts/${model.name}_${model.mel_spec.mel_spec_type}_${model.tokenizer}_${datasets.name}/${now:%Y-%m-%d}/${now:%H-%M-%S} + +datasets: + name: Emilia_ZH_EN + batch_size_per_gpu: 38400 # 8 GPUs, 8 * 38400 = 307200 + batch_size_type: frame # frame | sample + max_samples: 64 # max sequences per batch if use frame-wise batch_size. we set 32 for small models, 64 for base models + num_workers: 16 + +optim: + epochs: 11 + learning_rate: 7.5e-5 + num_warmup_updates: 20000 # warmup updates + grad_accumulation_steps: 1 # note: updates = steps / grad_accumulation_steps + max_grad_norm: 1.0 + bnb_optimizer: False + +model: + name: E2TTS_Small + tokenizer: pinyin + tokenizer_path: null # if 'custom' tokenizer, define the path want to use (should be vocab.txt) + backbone: UNetT + arch: + dim: 768 + depth: 20 + heads: 12 + ff_mult: 4 + text_mask_padding: False + pe_attn_head: 1 + mel_spec: + target_sample_rate: 24000 + n_mel_channels: 100 + hop_length: 256 + win_length: 1024 + n_fft: 1024 + mel_spec_type: vocos # vocos | bigvgan + vocoder: + is_local: False # use local offline ckpt or not + local_path: null # local vocoder path + +ckpts: + logger: wandb # wandb | tensorboard | null + log_samples: True # infer random sample per save checkpoint. wip, normal to fail with extra long samples + save_per_updates: 50000 # save checkpoint per updates + keep_last_n_checkpoints: -1 # -1 to keep all, 0 to not save intermediate, > 0 to keep last N checkpoints + last_per_updates: 5000 # save last checkpoint per updates + save_dir: ckpts/${model.name}_${model.mel_spec.mel_spec_type}_${model.tokenizer}_${datasets.name} \ No newline at end of file diff --git a/src/f5_tts/src/f5_tts/configs/F5TTS_Base.yaml b/src/f5_tts/src/f5_tts/configs/F5TTS_Base.yaml new file mode 100644 index 0000000000000000000000000000000000000000..30a7c314099d81a6354d5707288d7103449c2609 --- /dev/null +++ b/src/f5_tts/src/f5_tts/configs/F5TTS_Base.yaml @@ -0,0 +1,54 @@ +hydra: + run: + dir: ckpts/${model.name}_${model.mel_spec.mel_spec_type}_${model.tokenizer}_${datasets.name}/${now:%Y-%m-%d}/${now:%H-%M-%S} + +datasets: + name: Emilia_ZH_EN # dataset name + batch_size_per_gpu: 38400 # 8 GPUs, 8 * 38400 = 307200 + batch_size_type: frame # frame | sample + max_samples: 64 # max sequences per batch if use frame-wise batch_size. we set 32 for small models, 64 for base models + num_workers: 16 + +optim: + epochs: 11 + learning_rate: 7.5e-5 + num_warmup_updates: 20000 # warmup updates + grad_accumulation_steps: 1 # note: updates = steps / grad_accumulation_steps + max_grad_norm: 1.0 # gradient clipping + bnb_optimizer: False # use bnb 8bit AdamW optimizer or not + +model: + name: F5TTS_Base # model name + tokenizer: pinyin # tokenizer type + tokenizer_path: null # if 'custom' tokenizer, define the path want to use (should be vocab.txt) + backbone: DiT + arch: + dim: 1024 + depth: 22 + heads: 16 + ff_mult: 2 + text_dim: 512 + text_mask_padding: False + conv_layers: 4 + pe_attn_head: 1 + attn_backend: torch # torch | flash_attn + attn_mask_enabled: False + checkpoint_activations: False # recompute activations and save memory for extra compute + mel_spec: + target_sample_rate: 24000 + n_mel_channels: 100 + hop_length: 256 + win_length: 1024 + n_fft: 1024 + mel_spec_type: vocos # vocos | bigvgan + vocoder: + is_local: False # use local offline ckpt or not + local_path: null # local vocoder path + +ckpts: + logger: wandb # wandb | tensorboard | null + log_samples: True # infer random sample per save checkpoint. wip, normal to fail with extra long samples + save_per_updates: 50000 # save checkpoint per updates + keep_last_n_checkpoints: -1 # -1 to keep all, 0 to not save intermediate, > 0 to keep last N checkpoints + last_per_updates: 5000 # save last checkpoint per updates + save_dir: ckpts/${model.name}_${model.mel_spec.mel_spec_type}_${model.tokenizer}_${datasets.name} \ No newline at end of file diff --git a/src/f5_tts/src/f5_tts/configs/F5TTS_Small.yaml b/src/f5_tts/src/f5_tts/configs/F5TTS_Small.yaml new file mode 100644 index 0000000000000000000000000000000000000000..bee985ace22b59001938c4167b85853137c474c8 --- /dev/null +++ b/src/f5_tts/src/f5_tts/configs/F5TTS_Small.yaml @@ -0,0 +1,54 @@ +hydra: + run: + dir: ckpts/${model.name}_${model.mel_spec.mel_spec_type}_${model.tokenizer}_${datasets.name}/${now:%Y-%m-%d}/${now:%H-%M-%S} + +datasets: + name: Emilia_ZH_EN + batch_size_per_gpu: 38400 # 8 GPUs, 8 * 38400 = 307200 + batch_size_type: frame # frame | sample + max_samples: 64 # max sequences per batch if use frame-wise batch_size. we set 32 for small models, 64 for base models + num_workers: 16 + +optim: + epochs: 11 # only suitable for Emilia, if you want to train it on LibriTTS, set epoch 686 + learning_rate: 7.5e-5 + num_warmup_updates: 20000 # warmup updates + grad_accumulation_steps: 1 # note: updates = steps / grad_accumulation_steps + max_grad_norm: 1.0 # gradient clipping + bnb_optimizer: False # use bnb 8bit AdamW optimizer or not + +model: + name: F5TTS_Small + tokenizer: pinyin + tokenizer_path: null # if 'custom' tokenizer, define the path want to use (should be vocab.txt) + backbone: DiT + arch: + dim: 768 + depth: 18 + heads: 12 + ff_mult: 2 + text_dim: 512 + text_mask_padding: False + conv_layers: 4 + pe_attn_head: 1 + attn_backend: torch # torch | flash_attn + attn_mask_enabled: False + checkpoint_activations: False # recompute activations and save memory for extra compute + mel_spec: + target_sample_rate: 24000 + n_mel_channels: 100 + hop_length: 256 + win_length: 1024 + n_fft: 1024 + mel_spec_type: vocos # vocos | bigvgan + vocoder: + is_local: False # use local offline ckpt or not + local_path: null # local vocoder path + +ckpts: + logger: wandb # wandb | tensorboard | null + log_samples: True # infer random sample per save checkpoint. wip, normal to fail with extra long samples + save_per_updates: 50000 # save checkpoint per updates + keep_last_n_checkpoints: -1 # -1 to keep all, 0 to not save intermediate, > 0 to keep last N checkpoints + last_per_updates: 5000 # save last checkpoint per updates + save_dir: ckpts/${model.name}_${model.mel_spec.mel_spec_type}_${model.tokenizer}_${datasets.name} diff --git a/src/f5_tts/src/f5_tts/configs/F5TTS_v1_Base.yaml b/src/f5_tts/src/f5_tts/configs/F5TTS_v1_Base.yaml new file mode 100644 index 0000000000000000000000000000000000000000..40546a5f1d443676f6c7af4c98def50fbc6f19a9 --- /dev/null +++ b/src/f5_tts/src/f5_tts/configs/F5TTS_v1_Base.yaml @@ -0,0 +1,55 @@ +hydra: + run: + dir: ckpts/${model.name}_${model.mel_spec.mel_spec_type}_${model.tokenizer}_${datasets.name}/${now:%Y-%m-%d}/${now:%H-%M-%S} + +datasets: + name: Emilia_ZH_EN # dataset name + batch_size_per_gpu: 38400 # 8 GPUs, 8 * 38400 = 307200 + batch_size_type: frame # frame | sample + max_samples: 64 # max sequences per batch if use frame-wise batch_size. we set 32 for small models, 64 for base models + num_workers: 16 + +optim: + epochs: 11 + learning_rate: 7.5e-5 + num_warmup_updates: 20000 # warmup updates + grad_accumulation_steps: 1 # note: updates = steps / grad_accumulation_steps + max_grad_norm: 1.0 # gradient clipping + bnb_optimizer: False # use bnb 8bit AdamW optimizer or not + +model: + name: F5TTS_v1_Base # model name + tokenizer: pinyin # tokenizer type + tokenizer_path: null # if 'custom' tokenizer, define the path want to use (should be vocab.txt) + backbone: DiT + arch: + dim: 1024 + depth: 22 + heads: 16 + ff_mult: 2 + text_dim: 512 + text_mask_padding: True + qk_norm: null # null | rms_norm + conv_layers: 4 + pe_attn_head: null + attn_backend: torch # torch | flash_attn + attn_mask_enabled: False + checkpoint_activations: False # recompute activations and save memory for extra compute + mel_spec: + target_sample_rate: 24000 + n_mel_channels: 100 + hop_length: 256 + win_length: 1024 + n_fft: 1024 + mel_spec_type: vocos # vocos | bigvgan + vocoder: + is_local: False # use local offline ckpt or not + local_path: null # local vocoder path + +ckpts: + logger: wandb # wandb | tensorboard | null + log_samples: True # infer random sample per save checkpoint. wip, normal to fail with extra long samples + save_per_updates: 50000 # save checkpoint per updates + keep_last_n_checkpoints: -1 # -1 to keep all, 0 to not save intermediate, > 0 to keep last N checkpoints + last_per_updates: 5000 # save last checkpoint per updates + save_dir: ckpts/${model.name}_${model.mel_spec.mel_spec_type}_${model.tokenizer}_${datasets.name} \ No newline at end of file diff --git a/src/f5_tts/src/f5_tts/eval/README.md b/src/f5_tts/src/f5_tts/eval/README.md new file mode 100644 index 0000000000000000000000000000000000000000..79188f4a9520cd49f6daebbd6188d87de0a6ca0a --- /dev/null +++ b/src/f5_tts/src/f5_tts/eval/README.md @@ -0,0 +1,52 @@ + +# Evaluation + +Install packages for evaluation: + +```bash +pip install -e .[eval] +``` + +## Generating Samples for Evaluation + +### Prepare Test Datasets + +1. *Seed-TTS testset*: Download from [seed-tts-eval](https://github.com/BytedanceSpeech/seed-tts-eval). +2. *LibriSpeech test-clean*: Download from [OpenSLR](http://www.openslr.org/12/). +3. Unzip the downloaded datasets and place them in the `data/` directory. +4. Update the path for *LibriSpeech test-clean* data in `src/f5_tts/eval/eval_infer_batch.py` +5. Our filtered LibriSpeech-PC 4-10s subset: `data/librispeech_pc_test_clean_cross_sentence.lst` + +### Batch Inference for Test Set + +To run batch inference for evaluations, execute the following commands: + +```bash +# batch inference for evaluations +accelerate config # if not set before +bash src/f5_tts/eval/eval_infer_batch.sh +``` + +## Objective Evaluation on Generated Results + +### Download Evaluation Model Checkpoints + +1. Chinese ASR Model: [Paraformer-zh](https://huggingface.co/funasr/paraformer-zh) +2. English ASR Model: [Faster-Whisper](https://huggingface.co/Systran/faster-whisper-large-v3) +3. WavLM Model: Download from [Google Drive](https://drive.google.com/file/d/1-aE1NfzpRCLxA4GUxX9ITI3F9LlbtEGP/view). + +Then update in the following scripts with the paths you put evaluation model ckpts to. + +### Objective Evaluation + +Update the path with your batch-inferenced results, and carry out WER / SIM / UTMOS evaluations: +```bash +# Evaluation [WER] for Seed-TTS test [ZH] set +python src/f5_tts/eval/eval_seedtts_testset.py --eval_task wer --lang zh --gen_wav_dir --gpu_nums 8 + +# Evaluation [SIM] for LibriSpeech-PC test-clean (cross-sentence) +python src/f5_tts/eval/eval_librispeech_test_clean.py --eval_task sim --gen_wav_dir --librispeech_test_clean_path + +# Evaluation [UTMOS]. --ext: Audio extension +python src/f5_tts/eval/eval_utmos.py --audio_dir --ext wav +``` diff --git a/src/f5_tts/src/f5_tts/eval/ecapa_tdnn.py b/src/f5_tts/src/f5_tts/eval/ecapa_tdnn.py new file mode 100644 index 0000000000000000000000000000000000000000..f6c62b5a1156a3d119bdb1f2bd77c3e1885c090f --- /dev/null +++ b/src/f5_tts/src/f5_tts/eval/ecapa_tdnn.py @@ -0,0 +1,331 @@ +# just for speaker similarity evaluation, third-party code + +# From https://github.com/microsoft/UniSpeech/blob/main/downstreams/speaker_verification/models/ +# part of the code is borrowed from https://github.com/lawlict/ECAPA-TDNN + +import os + +import torch +import torch.nn as nn +import torch.nn.functional as F + + +""" Res2Conv1d + BatchNorm1d + ReLU +""" + + +class Res2Conv1dReluBn(nn.Module): + """ + in_channels == out_channels == channels + """ + + def __init__(self, channels, kernel_size=1, stride=1, padding=0, dilation=1, bias=True, scale=4): + super().__init__() + assert channels % scale == 0, "{} % {} != 0".format(channels, scale) + self.scale = scale + self.width = channels // scale + self.nums = scale if scale == 1 else scale - 1 + + self.convs = [] + self.bns = [] + for i in range(self.nums): + self.convs.append(nn.Conv1d(self.width, self.width, kernel_size, stride, padding, dilation, bias=bias)) + self.bns.append(nn.BatchNorm1d(self.width)) + self.convs = nn.ModuleList(self.convs) + self.bns = nn.ModuleList(self.bns) + + def forward(self, x): + out = [] + spx = torch.split(x, self.width, 1) + for i in range(self.nums): + if i == 0: + sp = spx[i] + else: + sp = sp + spx[i] + # Order: conv -> relu -> bn + sp = self.convs[i](sp) + sp = self.bns[i](F.relu(sp)) + out.append(sp) + if self.scale != 1: + out.append(spx[self.nums]) + out = torch.cat(out, dim=1) + + return out + + +""" Conv1d + BatchNorm1d + ReLU +""" + + +class Conv1dReluBn(nn.Module): + def __init__(self, in_channels, out_channels, kernel_size=1, stride=1, padding=0, dilation=1, bias=True): + super().__init__() + self.conv = nn.Conv1d(in_channels, out_channels, kernel_size, stride, padding, dilation, bias=bias) + self.bn = nn.BatchNorm1d(out_channels) + + def forward(self, x): + return self.bn(F.relu(self.conv(x))) + + +""" The SE connection of 1D case. +""" + + +class SE_Connect(nn.Module): + def __init__(self, channels, se_bottleneck_dim=128): + super().__init__() + self.linear1 = nn.Linear(channels, se_bottleneck_dim) + self.linear2 = nn.Linear(se_bottleneck_dim, channels) + + def forward(self, x): + out = x.mean(dim=2) + out = F.relu(self.linear1(out)) + out = torch.sigmoid(self.linear2(out)) + out = x * out.unsqueeze(2) + + return out + + +""" SE-Res2Block of the ECAPA-TDNN architecture. +""" + +# def SE_Res2Block(channels, kernel_size, stride, padding, dilation, scale): +# return nn.Sequential( +# Conv1dReluBn(channels, 512, kernel_size=1, stride=1, padding=0), +# Res2Conv1dReluBn(512, kernel_size, stride, padding, dilation, scale=scale), +# Conv1dReluBn(512, channels, kernel_size=1, stride=1, padding=0), +# SE_Connect(channels) +# ) + + +class SE_Res2Block(nn.Module): + def __init__(self, in_channels, out_channels, kernel_size, stride, padding, dilation, scale, se_bottleneck_dim): + super().__init__() + self.Conv1dReluBn1 = Conv1dReluBn(in_channels, out_channels, kernel_size=1, stride=1, padding=0) + self.Res2Conv1dReluBn = Res2Conv1dReluBn(out_channels, kernel_size, stride, padding, dilation, scale=scale) + self.Conv1dReluBn2 = Conv1dReluBn(out_channels, out_channels, kernel_size=1, stride=1, padding=0) + self.SE_Connect = SE_Connect(out_channels, se_bottleneck_dim) + + self.shortcut = None + if in_channels != out_channels: + self.shortcut = nn.Conv1d( + in_channels=in_channels, + out_channels=out_channels, + kernel_size=1, + ) + + def forward(self, x): + residual = x + if self.shortcut: + residual = self.shortcut(x) + + x = self.Conv1dReluBn1(x) + x = self.Res2Conv1dReluBn(x) + x = self.Conv1dReluBn2(x) + x = self.SE_Connect(x) + + return x + residual + + +""" Attentive weighted mean and standard deviation pooling. +""" + + +class AttentiveStatsPool(nn.Module): + def __init__(self, in_dim, attention_channels=128, global_context_att=False): + super().__init__() + self.global_context_att = global_context_att + + # Use Conv1d with stride == 1 rather than Linear, then we don't need to transpose inputs. + if global_context_att: + self.linear1 = nn.Conv1d(in_dim * 3, attention_channels, kernel_size=1) # equals W and b in the paper + else: + self.linear1 = nn.Conv1d(in_dim, attention_channels, kernel_size=1) # equals W and b in the paper + self.linear2 = nn.Conv1d(attention_channels, in_dim, kernel_size=1) # equals V and k in the paper + + def forward(self, x): + if self.global_context_att: + context_mean = torch.mean(x, dim=-1, keepdim=True).expand_as(x) + context_std = torch.sqrt(torch.var(x, dim=-1, keepdim=True) + 1e-10).expand_as(x) + x_in = torch.cat((x, context_mean, context_std), dim=1) + else: + x_in = x + + # DON'T use ReLU here! In experiments, I find ReLU hard to converge. + alpha = torch.tanh(self.linear1(x_in)) + # alpha = F.relu(self.linear1(x_in)) + alpha = torch.softmax(self.linear2(alpha), dim=2) + mean = torch.sum(alpha * x, dim=2) + residuals = torch.sum(alpha * (x**2), dim=2) - mean**2 + std = torch.sqrt(residuals.clamp(min=1e-9)) + return torch.cat([mean, std], dim=1) + + +class ECAPA_TDNN(nn.Module): + def __init__( + self, + feat_dim=80, + channels=512, + emb_dim=192, + global_context_att=False, + feat_type="wavlm_large", + sr=16000, + feature_selection="hidden_states", + update_extract=False, + config_path=None, + ): + super().__init__() + + self.feat_type = feat_type + self.feature_selection = feature_selection + self.update_extract = update_extract + self.sr = sr + + torch.hub._validate_not_a_forked_repo = lambda a, b, c: True + try: + local_s3prl_path = os.path.expanduser("~/.cache/torch/hub/s3prl_s3prl_main") + self.feature_extract = torch.hub.load(local_s3prl_path, feat_type, source="local", config_path=config_path) + except: # noqa: E722 + self.feature_extract = torch.hub.load("s3prl/s3prl", feat_type) + + if len(self.feature_extract.model.encoder.layers) == 24 and hasattr( + self.feature_extract.model.encoder.layers[23].self_attn, "fp32_attention" + ): + self.feature_extract.model.encoder.layers[23].self_attn.fp32_attention = False + if len(self.feature_extract.model.encoder.layers) == 24 and hasattr( + self.feature_extract.model.encoder.layers[11].self_attn, "fp32_attention" + ): + self.feature_extract.model.encoder.layers[11].self_attn.fp32_attention = False + + self.feat_num = self.get_feat_num() + self.feature_weight = nn.Parameter(torch.zeros(self.feat_num)) + + if feat_type != "fbank" and feat_type != "mfcc": + freeze_list = ["final_proj", "label_embs_concat", "mask_emb", "project_q", "quantizer"] + for name, param in self.feature_extract.named_parameters(): + for freeze_val in freeze_list: + if freeze_val in name: + param.requires_grad = False + break + + if not self.update_extract: + for param in self.feature_extract.parameters(): + param.requires_grad = False + + self.instance_norm = nn.InstanceNorm1d(feat_dim) + # self.channels = [channels] * 4 + [channels * 3] + self.channels = [channels] * 4 + [1536] + + self.layer1 = Conv1dReluBn(feat_dim, self.channels[0], kernel_size=5, padding=2) + self.layer2 = SE_Res2Block( + self.channels[0], + self.channels[1], + kernel_size=3, + stride=1, + padding=2, + dilation=2, + scale=8, + se_bottleneck_dim=128, + ) + self.layer3 = SE_Res2Block( + self.channels[1], + self.channels[2], + kernel_size=3, + stride=1, + padding=3, + dilation=3, + scale=8, + se_bottleneck_dim=128, + ) + self.layer4 = SE_Res2Block( + self.channels[2], + self.channels[3], + kernel_size=3, + stride=1, + padding=4, + dilation=4, + scale=8, + se_bottleneck_dim=128, + ) + + # self.conv = nn.Conv1d(self.channels[-1], self.channels[-1], kernel_size=1) + cat_channels = channels * 3 + self.conv = nn.Conv1d(cat_channels, self.channels[-1], kernel_size=1) + self.pooling = AttentiveStatsPool( + self.channels[-1], attention_channels=128, global_context_att=global_context_att + ) + self.bn = nn.BatchNorm1d(self.channels[-1] * 2) + self.linear = nn.Linear(self.channels[-1] * 2, emb_dim) + + def get_feat_num(self): + self.feature_extract.eval() + wav = [torch.randn(self.sr).to(next(self.feature_extract.parameters()).device)] + with torch.no_grad(): + features = self.feature_extract(wav) + select_feature = features[self.feature_selection] + if isinstance(select_feature, (list, tuple)): + return len(select_feature) + else: + return 1 + + def get_feat(self, x): + if self.update_extract: + x = self.feature_extract([sample for sample in x]) + else: + with torch.no_grad(): + if self.feat_type == "fbank" or self.feat_type == "mfcc": + x = self.feature_extract(x) + 1e-6 # B x feat_dim x time_len + else: + x = self.feature_extract([sample for sample in x]) + + if self.feat_type == "fbank": + x = x.log() + + if self.feat_type != "fbank" and self.feat_type != "mfcc": + x = x[self.feature_selection] + if isinstance(x, (list, tuple)): + x = torch.stack(x, dim=0) + else: + x = x.unsqueeze(0) + norm_weights = F.softmax(self.feature_weight, dim=-1).unsqueeze(-1).unsqueeze(-1).unsqueeze(-1) + x = (norm_weights * x).sum(dim=0) + x = torch.transpose(x, 1, 2) + 1e-6 + + x = self.instance_norm(x) + return x + + def forward(self, x): + x = self.get_feat(x) + + out1 = self.layer1(x) + out2 = self.layer2(out1) + out3 = self.layer3(out2) + out4 = self.layer4(out3) + + out = torch.cat([out2, out3, out4], dim=1) + out = F.relu(self.conv(out)) + out = self.bn(self.pooling(out)) + out = self.linear(out) + + return out + + +def ECAPA_TDNN_SMALL( + feat_dim, + emb_dim=256, + feat_type="wavlm_large", + sr=16000, + feature_selection="hidden_states", + update_extract=False, + config_path=None, +): + return ECAPA_TDNN( + feat_dim=feat_dim, + channels=512, + emb_dim=emb_dim, + feat_type=feat_type, + sr=sr, + feature_selection=feature_selection, + update_extract=update_extract, + config_path=config_path, + ) diff --git a/src/f5_tts/src/f5_tts/eval/eval_infer_batch.py b/src/f5_tts/src/f5_tts/eval/eval_infer_batch.py new file mode 100644 index 0000000000000000000000000000000000000000..90a5c416c669ed1e18f180588b3da48c6f8eb72b --- /dev/null +++ b/src/f5_tts/src/f5_tts/eval/eval_infer_batch.py @@ -0,0 +1,210 @@ +import os +import sys + + +sys.path.append(os.getcwd()) + +import argparse +import time +from importlib.resources import files + +import torch +import torchaudio +from accelerate import Accelerator +from hydra.utils import get_class +from omegaconf import OmegaConf +from tqdm import tqdm + +from f5_tts.eval.utils_eval import ( + get_inference_prompt, + get_librispeech_test_clean_metainfo, + get_seedtts_testset_metainfo, +) +from f5_tts.infer.utils_infer import load_checkpoint, load_vocoder +from f5_tts.model import CFM +from f5_tts.model.utils import get_tokenizer + + +accelerator = Accelerator() +device = f"cuda:{accelerator.process_index}" + + +use_ema = True +target_rms = 0.1 + + +rel_path = str(files("f5_tts").joinpath("../../")) + + +def main(): + parser = argparse.ArgumentParser(description="batch inference") + + parser.add_argument("-s", "--seed", default=None, type=int) + parser.add_argument("-n", "--expname", required=True) + parser.add_argument("-c", "--ckptstep", default=1250000, type=int) + + parser.add_argument("-nfe", "--nfestep", default=32, type=int) + parser.add_argument("-o", "--odemethod", default="euler") + parser.add_argument("-ss", "--swaysampling", default=-1, type=float) + + parser.add_argument("-t", "--testset", required=True) + + args = parser.parse_args() + + seed = args.seed + exp_name = args.expname + ckpt_step = args.ckptstep + + nfe_step = args.nfestep + ode_method = args.odemethod + sway_sampling_coef = args.swaysampling + + testset = args.testset + + infer_batch_size = 1 # max frames. 1 for ddp single inference (recommended) + cfg_strength = 2.0 + speed = 1.0 + use_truth_duration = False + no_ref_audio = False + + model_cfg = OmegaConf.load(str(files("f5_tts").joinpath(f"configs/{exp_name}.yaml"))) + model_cls = get_class(f"f5_tts.model.{model_cfg.model.backbone}") + model_arc = model_cfg.model.arch + + dataset_name = model_cfg.datasets.name + tokenizer = model_cfg.model.tokenizer + + mel_spec_type = model_cfg.model.mel_spec.mel_spec_type + target_sample_rate = model_cfg.model.mel_spec.target_sample_rate + n_mel_channels = model_cfg.model.mel_spec.n_mel_channels + hop_length = model_cfg.model.mel_spec.hop_length + win_length = model_cfg.model.mel_spec.win_length + n_fft = model_cfg.model.mel_spec.n_fft + + if testset == "ls_pc_test_clean": + metalst = rel_path + "/data/librispeech_pc_test_clean_cross_sentence.lst" + librispeech_test_clean_path = "/LibriSpeech/test-clean" # test-clean path + metainfo = get_librispeech_test_clean_metainfo(metalst, librispeech_test_clean_path) + + elif testset == "seedtts_test_zh": + metalst = rel_path + "/data/seedtts_testset/zh/meta.lst" + metainfo = get_seedtts_testset_metainfo(metalst) + + elif testset == "seedtts_test_en": + metalst = rel_path + "/data/seedtts_testset/en/meta.lst" + metainfo = get_seedtts_testset_metainfo(metalst) + + # path to save genereted wavs + output_dir = ( + f"{rel_path}/" + f"results/{exp_name}_{ckpt_step}/{testset}/" + f"seed{seed}_{ode_method}_nfe{nfe_step}_{mel_spec_type}" + f"{f'_ss{sway_sampling_coef}' if sway_sampling_coef else ''}" + f"_cfg{cfg_strength}_speed{speed}" + f"{'_gt-dur' if use_truth_duration else ''}" + f"{'_no-ref-audio' if no_ref_audio else ''}" + ) + + # -------------------------------------------------# + + prompts_all = get_inference_prompt( + metainfo, + speed=speed, + tokenizer=tokenizer, + target_sample_rate=target_sample_rate, + n_mel_channels=n_mel_channels, + hop_length=hop_length, + mel_spec_type=mel_spec_type, + target_rms=target_rms, + use_truth_duration=use_truth_duration, + infer_batch_size=infer_batch_size, + ) + + # Vocoder model + local = False + if mel_spec_type == "vocos": + vocoder_local_path = "../checkpoints/charactr/vocos-mel-24khz" + elif mel_spec_type == "bigvgan": + vocoder_local_path = "../checkpoints/bigvgan_v2_24khz_100band_256x" + vocoder = load_vocoder(vocoder_name=mel_spec_type, is_local=local, local_path=vocoder_local_path) + + # Tokenizer + vocab_char_map, vocab_size = get_tokenizer(dataset_name, tokenizer) + + # Model + model = CFM( + transformer=model_cls(**model_arc, text_num_embeds=vocab_size, mel_dim=n_mel_channels), + mel_spec_kwargs=dict( + n_fft=n_fft, + hop_length=hop_length, + win_length=win_length, + n_mel_channels=n_mel_channels, + target_sample_rate=target_sample_rate, + mel_spec_type=mel_spec_type, + ), + odeint_kwargs=dict( + method=ode_method, + ), + vocab_char_map=vocab_char_map, + ).to(device) + + ckpt_prefix = rel_path + f"/ckpts/{exp_name}/model_{ckpt_step}" + if os.path.exists(ckpt_prefix + ".pt"): + ckpt_path = ckpt_prefix + ".pt" + elif os.path.exists(ckpt_prefix + ".safetensors"): + ckpt_path = ckpt_prefix + ".safetensors" + else: + print("Loading from self-organized training checkpoints rather than released pretrained.") + ckpt_path = rel_path + f"/{model_cfg.ckpts.save_dir}/model_{ckpt_step}.pt" + + dtype = torch.float32 if mel_spec_type == "bigvgan" else None + model = load_checkpoint(model, ckpt_path, device, dtype=dtype, use_ema=use_ema) + + if not os.path.exists(output_dir) and accelerator.is_main_process: + os.makedirs(output_dir) + + # start batch inference + accelerator.wait_for_everyone() + start = time.time() + + with accelerator.split_between_processes(prompts_all) as prompts: + for prompt in tqdm(prompts, disable=not accelerator.is_local_main_process): + utts, ref_rms_list, ref_mels, ref_mel_lens, total_mel_lens, final_text_list = prompt + ref_mels = ref_mels.to(device) + ref_mel_lens = torch.tensor(ref_mel_lens, dtype=torch.long).to(device) + total_mel_lens = torch.tensor(total_mel_lens, dtype=torch.long).to(device) + + # Inference + with torch.inference_mode(): + generated, _ = model.sample( + cond=ref_mels, + text=final_text_list, + duration=total_mel_lens, + lens=ref_mel_lens, + steps=nfe_step, + cfg_strength=cfg_strength, + sway_sampling_coef=sway_sampling_coef, + no_ref_audio=no_ref_audio, + seed=seed, + ) + # Final result + for i, gen in enumerate(generated): + gen = gen[ref_mel_lens[i] : total_mel_lens[i], :].unsqueeze(0) + gen_mel_spec = gen.permute(0, 2, 1).to(torch.float32) + if mel_spec_type == "vocos": + generated_wave = vocoder.decode(gen_mel_spec).cpu() + elif mel_spec_type == "bigvgan": + generated_wave = vocoder(gen_mel_spec).squeeze(0).cpu() + + if ref_rms_list[i] < target_rms: + generated_wave = generated_wave * ref_rms_list[i] / target_rms + torchaudio.save(f"{output_dir}/{utts[i]}.wav", generated_wave, target_sample_rate) + + accelerator.wait_for_everyone() + if accelerator.is_main_process: + timediff = time.time() - start + print(f"Done batch inference in {timediff / 60:.2f} minutes.") + + +if __name__ == "__main__": + main() diff --git a/src/f5_tts/src/f5_tts/eval/eval_infer_batch.sh b/src/f5_tts/src/f5_tts/eval/eval_infer_batch.sh new file mode 100644 index 0000000000000000000000000000000000000000..e2be077afe64125eded5a5e9fd35c31305608dc7 --- /dev/null +++ b/src/f5_tts/src/f5_tts/eval/eval_infer_batch.sh @@ -0,0 +1,18 @@ +#!/bin/bash + +# e.g. F5-TTS, 16 NFE +accelerate launch src/f5_tts/eval/eval_infer_batch.py -s 0 -n "F5TTS_v1_Base" -t "seedtts_test_zh" -nfe 16 +accelerate launch src/f5_tts/eval/eval_infer_batch.py -s 0 -n "F5TTS_v1_Base" -t "seedtts_test_en" -nfe 16 +accelerate launch src/f5_tts/eval/eval_infer_batch.py -s 0 -n "F5TTS_v1_Base" -t "ls_pc_test_clean" -nfe 16 + +# e.g. Vanilla E2 TTS, 32 NFE +accelerate launch src/f5_tts/eval/eval_infer_batch.py -s 0 -n "E2TTS_Base" -c 1200000 -t "seedtts_test_zh" -o "midpoint" -ss 0 +accelerate launch src/f5_tts/eval/eval_infer_batch.py -s 0 -n "E2TTS_Base" -c 1200000 -t "seedtts_test_en" -o "midpoint" -ss 0 +accelerate launch src/f5_tts/eval/eval_infer_batch.py -s 0 -n "E2TTS_Base" -c 1200000 -t "ls_pc_test_clean" -o "midpoint" -ss 0 + +# e.g. evaluate F5-TTS 16 NFE result on Seed-TTS test-zh +python src/f5_tts/eval/eval_seedtts_testset.py -e wer -l zh --gen_wav_dir results/F5TTS_v1_Base_1250000/seedtts_test_zh/seed0_euler_nfe32_vocos_ss-1_cfg2.0_speed1.0 --gpu_nums 8 +python src/f5_tts/eval/eval_seedtts_testset.py -e sim -l zh --gen_wav_dir results/F5TTS_v1_Base_1250000/seedtts_test_zh/seed0_euler_nfe32_vocos_ss-1_cfg2.0_speed1.0 --gpu_nums 8 +python src/f5_tts/eval/eval_utmos.py --audio_dir results/F5TTS_v1_Base_1250000/seedtts_test_zh/seed0_euler_nfe32_vocos_ss-1_cfg2.0_speed1.0 + +# etc. diff --git a/src/f5_tts/src/f5_tts/eval/eval_librispeech_test_clean.py b/src/f5_tts/src/f5_tts/eval/eval_librispeech_test_clean.py new file mode 100644 index 0000000000000000000000000000000000000000..0bd75e79ce011da9bbb856abf90e487c97a6263c --- /dev/null +++ b/src/f5_tts/src/f5_tts/eval/eval_librispeech_test_clean.py @@ -0,0 +1,89 @@ +# Evaluate with Librispeech test-clean, ~3s prompt to generate 4-10s audio (the way of valle/voicebox evaluation) + +import argparse +import json +import os +import sys + + +sys.path.append(os.getcwd()) + +import multiprocessing as mp +from importlib.resources import files + +import numpy as np + +from f5_tts.eval.utils_eval import get_librispeech_test, run_asr_wer, run_sim + + +rel_path = str(files("f5_tts").joinpath("../../")) + + +def get_args(): + parser = argparse.ArgumentParser() + parser.add_argument("-e", "--eval_task", type=str, default="wer", choices=["sim", "wer"]) + parser.add_argument("-l", "--lang", type=str, default="en") + parser.add_argument("-g", "--gen_wav_dir", type=str, required=True) + parser.add_argument("-p", "--librispeech_test_clean_path", type=str, required=True) + parser.add_argument("-n", "--gpu_nums", type=int, default=8, help="Number of GPUs to use") + parser.add_argument("--local", action="store_true", help="Use local custom checkpoint directory") + return parser.parse_args() + + +def main(): + args = get_args() + eval_task = args.eval_task + lang = args.lang + librispeech_test_clean_path = args.librispeech_test_clean_path # test-clean path + gen_wav_dir = args.gen_wav_dir + metalst = rel_path + "/data/librispeech_pc_test_clean_cross_sentence.lst" + + gpus = list(range(args.gpu_nums)) + test_set = get_librispeech_test(metalst, gen_wav_dir, gpus, librispeech_test_clean_path) + + ## In LibriSpeech, some speakers utilized varying voice characteristics for different characters in the book, + ## leading to a low similarity for the ground truth in some cases. + # test_set = get_librispeech_test(metalst, gen_wav_dir, gpus, librispeech_test_clean_path, eval_ground_truth = True) # eval ground truth + + local = args.local + if local: # use local custom checkpoint dir + asr_ckpt_dir = "../checkpoints/Systran/faster-whisper-large-v3" + else: + asr_ckpt_dir = "" # auto download to cache dir + wavlm_ckpt_dir = "../checkpoints/UniSpeech/wavlm_large_finetune.pth" + + # -------------------------------------------------------------------------- + + full_results = [] + metrics = [] + + if eval_task == "wer": + with mp.Pool(processes=len(gpus)) as pool: + args = [(rank, lang, sub_test_set, asr_ckpt_dir) for (rank, sub_test_set) in test_set] + results = pool.map(run_asr_wer, args) + for r in results: + full_results.extend(r) + elif eval_task == "sim": + with mp.Pool(processes=len(gpus)) as pool: + args = [(rank, sub_test_set, wavlm_ckpt_dir) for (rank, sub_test_set) in test_set] + results = pool.map(run_sim, args) + for r in results: + full_results.extend(r) + else: + raise ValueError(f"Unknown metric type: {eval_task}") + + result_path = f"{gen_wav_dir}/_{eval_task}_results.jsonl" + with open(result_path, "w") as f: + for line in full_results: + metrics.append(line[eval_task]) + f.write(json.dumps(line, ensure_ascii=False) + "\n") + metric = round(np.mean(metrics), 5) + f.write(f"\n{eval_task.upper()}: {metric}\n") + + print(f"\nTotal {len(metrics)} samples") + print(f"{eval_task.upper()}: {metric}") + print(f"{eval_task.upper()} results saved to {result_path}") + + +if __name__ == "__main__": + main() diff --git a/src/f5_tts/src/f5_tts/eval/eval_seedtts_testset.py b/src/f5_tts/src/f5_tts/eval/eval_seedtts_testset.py new file mode 100644 index 0000000000000000000000000000000000000000..1d5b8f8abbd5c651d529c15c8f4bd3400756ddd4 --- /dev/null +++ b/src/f5_tts/src/f5_tts/eval/eval_seedtts_testset.py @@ -0,0 +1,88 @@ +# Evaluate with Seed-TTS testset + +import argparse +import json +import os +import sys + + +sys.path.append(os.getcwd()) + +import multiprocessing as mp +from importlib.resources import files + +import numpy as np + +from f5_tts.eval.utils_eval import get_seed_tts_test, run_asr_wer, run_sim + + +rel_path = str(files("f5_tts").joinpath("../../")) + + +def get_args(): + parser = argparse.ArgumentParser() + parser.add_argument("-e", "--eval_task", type=str, default="wer", choices=["sim", "wer"]) + parser.add_argument("-l", "--lang", type=str, default="en", choices=["zh", "en"]) + parser.add_argument("-g", "--gen_wav_dir", type=str, required=True) + parser.add_argument("-n", "--gpu_nums", type=int, default=8, help="Number of GPUs to use") + parser.add_argument("--local", action="store_true", help="Use local custom checkpoint directory") + return parser.parse_args() + + +def main(): + args = get_args() + eval_task = args.eval_task + lang = args.lang + gen_wav_dir = args.gen_wav_dir + metalst = rel_path + f"/data/seedtts_testset/{lang}/meta.lst" # seed-tts testset + + # NOTE. paraformer-zh result will be slightly different according to the number of gpus, cuz batchsize is different + # zh 1.254 seems a result of 4 workers wer_seed_tts + gpus = list(range(args.gpu_nums)) + test_set = get_seed_tts_test(metalst, gen_wav_dir, gpus) + + local = args.local + if local: # use local custom checkpoint dir + if lang == "zh": + asr_ckpt_dir = "../checkpoints/funasr" # paraformer-zh dir under funasr + elif lang == "en": + asr_ckpt_dir = "../checkpoints/Systran/faster-whisper-large-v3" + else: + asr_ckpt_dir = "" # auto download to cache dir + wavlm_ckpt_dir = "../checkpoints/UniSpeech/wavlm_large_finetune.pth" + + # -------------------------------------------------------------------------- + + full_results = [] + metrics = [] + + if eval_task == "wer": + with mp.Pool(processes=len(gpus)) as pool: + args = [(rank, lang, sub_test_set, asr_ckpt_dir) for (rank, sub_test_set) in test_set] + results = pool.map(run_asr_wer, args) + for r in results: + full_results.extend(r) + elif eval_task == "sim": + with mp.Pool(processes=len(gpus)) as pool: + args = [(rank, sub_test_set, wavlm_ckpt_dir) for (rank, sub_test_set) in test_set] + results = pool.map(run_sim, args) + for r in results: + full_results.extend(r) + else: + raise ValueError(f"Unknown metric type: {eval_task}") + + result_path = f"{gen_wav_dir}/_{eval_task}_results.jsonl" + with open(result_path, "w") as f: + for line in full_results: + metrics.append(line[eval_task]) + f.write(json.dumps(line, ensure_ascii=False) + "\n") + metric = round(np.mean(metrics), 5) + f.write(f"\n{eval_task.upper()}: {metric}\n") + + print(f"\nTotal {len(metrics)} samples") + print(f"{eval_task.upper()}: {metric}") + print(f"{eval_task.upper()} results saved to {result_path}") + + +if __name__ == "__main__": + main() diff --git a/src/f5_tts/src/f5_tts/eval/eval_utmos.py b/src/f5_tts/src/f5_tts/eval/eval_utmos.py new file mode 100644 index 0000000000000000000000000000000000000000..92db86b603258ce210001ab526c0c61937d8140b --- /dev/null +++ b/src/f5_tts/src/f5_tts/eval/eval_utmos.py @@ -0,0 +1,42 @@ +import argparse +import json +from pathlib import Path + +import librosa +import torch +from tqdm import tqdm + + +def main(): + parser = argparse.ArgumentParser(description="UTMOS Evaluation") + parser.add_argument("--audio_dir", type=str, required=True, help="Audio file path.") + parser.add_argument("--ext", type=str, default="wav", help="Audio extension.") + args = parser.parse_args() + + device = "cuda" if torch.cuda.is_available() else "xpu" if torch.xpu.is_available() else "cpu" + + predictor = torch.hub.load("tarepan/SpeechMOS:v1.2.0", "utmos22_strong", trust_repo=True) + predictor = predictor.to(device) + + audio_paths = list(Path(args.audio_dir).rglob(f"*.{args.ext}")) + utmos_score = 0 + + utmos_result_path = Path(args.audio_dir) / "_utmos_results.jsonl" + with open(utmos_result_path, "w", encoding="utf-8") as f: + for audio_path in tqdm(audio_paths, desc="Processing"): + wav, sr = librosa.load(audio_path, sr=None, mono=True) + wav_tensor = torch.from_numpy(wav).to(device).unsqueeze(0) + score = predictor(wav_tensor, sr) + line = {} + line["wav"], line["utmos"] = str(audio_path.stem), score.item() + utmos_score += score.item() + f.write(json.dumps(line, ensure_ascii=False) + "\n") + avg_score = utmos_score / len(audio_paths) if len(audio_paths) > 0 else 0 + f.write(f"\nUTMOS: {avg_score:.4f}\n") + + print(f"UTMOS: {avg_score:.4f}") + print(f"UTMOS results saved to {utmos_result_path}") + + +if __name__ == "__main__": + main() diff --git a/src/f5_tts/src/f5_tts/eval/utils_eval.py b/src/f5_tts/src/f5_tts/eval/utils_eval.py new file mode 100644 index 0000000000000000000000000000000000000000..b6391fb991d0a2ebb4e24bc2945f03fe3a120ebe --- /dev/null +++ b/src/f5_tts/src/f5_tts/eval/utils_eval.py @@ -0,0 +1,419 @@ +import math +import os +import random +import string +from pathlib import Path + +import torch +import torch.nn.functional as F +import torchaudio +from tqdm import tqdm + +from f5_tts.eval.ecapa_tdnn import ECAPA_TDNN_SMALL +from f5_tts.model.modules import MelSpec +from f5_tts.model.utils import convert_char_to_pinyin + + +# seedtts testset metainfo: utt, prompt_text, prompt_wav, gt_text, gt_wav +def get_seedtts_testset_metainfo(metalst): + f = open(metalst) + lines = f.readlines() + f.close() + metainfo = [] + for line in lines: + if len(line.strip().split("|")) == 5: + utt, prompt_text, prompt_wav, gt_text, gt_wav = line.strip().split("|") + elif len(line.strip().split("|")) == 4: + utt, prompt_text, prompt_wav, gt_text = line.strip().split("|") + gt_wav = os.path.join(os.path.dirname(metalst), "wavs", utt + ".wav") + if not os.path.isabs(prompt_wav): + prompt_wav = os.path.join(os.path.dirname(metalst), prompt_wav) + metainfo.append((utt, prompt_text, prompt_wav, gt_text, gt_wav)) + return metainfo + + +# librispeech test-clean metainfo: gen_utt, ref_txt, ref_wav, gen_txt, gen_wav +def get_librispeech_test_clean_metainfo(metalst, librispeech_test_clean_path): + f = open(metalst) + lines = f.readlines() + f.close() + metainfo = [] + for line in lines: + ref_utt, ref_dur, ref_txt, gen_utt, gen_dur, gen_txt = line.strip().split("\t") + + # ref_txt = ref_txt[0] + ref_txt[1:].lower() + '.' # if use librispeech test-clean (no-pc) + ref_spk_id, ref_chaptr_id, _ = ref_utt.split("-") + ref_wav = os.path.join(librispeech_test_clean_path, ref_spk_id, ref_chaptr_id, ref_utt + ".flac") + + # gen_txt = gen_txt[0] + gen_txt[1:].lower() + '.' # if use librispeech test-clean (no-pc) + gen_spk_id, gen_chaptr_id, _ = gen_utt.split("-") + gen_wav = os.path.join(librispeech_test_clean_path, gen_spk_id, gen_chaptr_id, gen_utt + ".flac") + + metainfo.append((gen_utt, ref_txt, ref_wav, " " + gen_txt, gen_wav)) + + return metainfo + + +# padded to max length mel batch +def padded_mel_batch(ref_mels): + max_mel_length = torch.LongTensor([mel.shape[-1] for mel in ref_mels]).amax() + padded_ref_mels = [] + for mel in ref_mels: + padded_ref_mel = F.pad(mel, (0, max_mel_length - mel.shape[-1]), value=0) + padded_ref_mels.append(padded_ref_mel) + padded_ref_mels = torch.stack(padded_ref_mels) + padded_ref_mels = padded_ref_mels.permute(0, 2, 1) + return padded_ref_mels + + +# get prompts from metainfo containing: utt, prompt_text, prompt_wav, gt_text, gt_wav + + +def get_inference_prompt( + metainfo, + speed=1.0, + tokenizer="pinyin", + polyphone=True, + target_sample_rate=24000, + n_fft=1024, + win_length=1024, + n_mel_channels=100, + hop_length=256, + mel_spec_type="vocos", + target_rms=0.1, + use_truth_duration=False, + infer_batch_size=1, + num_buckets=200, + min_secs=3, + max_secs=40, +): + prompts_all = [] + + min_tokens = min_secs * target_sample_rate // hop_length + max_tokens = max_secs * target_sample_rate // hop_length + + batch_accum = [0] * num_buckets + utts, ref_rms_list, ref_mels, ref_mel_lens, total_mel_lens, final_text_list = ( + [[] for _ in range(num_buckets)] for _ in range(6) + ) + + mel_spectrogram = MelSpec( + n_fft=n_fft, + hop_length=hop_length, + win_length=win_length, + n_mel_channels=n_mel_channels, + target_sample_rate=target_sample_rate, + mel_spec_type=mel_spec_type, + ) + + for utt, prompt_text, prompt_wav, gt_text, gt_wav in tqdm(metainfo, desc="Processing prompts..."): + # Audio + ref_audio, ref_sr = torchaudio.load(prompt_wav) + ref_rms = torch.sqrt(torch.mean(torch.square(ref_audio))) + if ref_rms < target_rms: + ref_audio = ref_audio * target_rms / ref_rms + assert ref_audio.shape[-1] > 5000, f"Empty prompt wav: {prompt_wav}, or torchaudio backend issue." + if ref_sr != target_sample_rate: + resampler = torchaudio.transforms.Resample(ref_sr, target_sample_rate) + ref_audio = resampler(ref_audio) + + # Text + if len(prompt_text[-1].encode("utf-8")) == 1: + prompt_text = prompt_text + " " + text = [prompt_text + gt_text] + if tokenizer == "pinyin": + text_list = convert_char_to_pinyin(text, polyphone=polyphone) + else: + text_list = text + + # to mel spectrogram + ref_mel = mel_spectrogram(ref_audio) + ref_mel = ref_mel.squeeze(0) + + # Duration, mel frame length + ref_mel_len = ref_mel.shape[-1] + + if use_truth_duration: + gt_audio, gt_sr = torchaudio.load(gt_wav) + if gt_sr != target_sample_rate: + resampler = torchaudio.transforms.Resample(gt_sr, target_sample_rate) + gt_audio = resampler(gt_audio) + total_mel_len = ref_mel_len + int(gt_audio.shape[-1] / hop_length / speed) + + # # test vocoder resynthesis + # ref_audio = gt_audio + else: + ref_text_len = len(prompt_text.encode("utf-8")) + gen_text_len = len(gt_text.encode("utf-8")) + total_mel_len = ref_mel_len + int(ref_mel_len / ref_text_len * gen_text_len / speed) + + # deal with batch + assert infer_batch_size > 0, "infer_batch_size should be greater than 0." + assert min_tokens <= total_mel_len <= max_tokens, ( + f"Audio {utt} has duration {total_mel_len * hop_length // target_sample_rate}s out of range [{min_secs}, {max_secs}]." + ) + bucket_i = math.floor((total_mel_len - min_tokens) / (max_tokens - min_tokens + 1) * num_buckets) + + utts[bucket_i].append(utt) + ref_rms_list[bucket_i].append(ref_rms) + ref_mels[bucket_i].append(ref_mel) + ref_mel_lens[bucket_i].append(ref_mel_len) + total_mel_lens[bucket_i].append(total_mel_len) + final_text_list[bucket_i].extend(text_list) + + batch_accum[bucket_i] += total_mel_len + + if batch_accum[bucket_i] >= infer_batch_size: + # print(f"\n{len(ref_mels[bucket_i][0][0])}\n{ref_mel_lens[bucket_i]}\n{total_mel_lens[bucket_i]}") + prompts_all.append( + ( + utts[bucket_i], + ref_rms_list[bucket_i], + padded_mel_batch(ref_mels[bucket_i]), + ref_mel_lens[bucket_i], + total_mel_lens[bucket_i], + final_text_list[bucket_i], + ) + ) + batch_accum[bucket_i] = 0 + ( + utts[bucket_i], + ref_rms_list[bucket_i], + ref_mels[bucket_i], + ref_mel_lens[bucket_i], + total_mel_lens[bucket_i], + final_text_list[bucket_i], + ) = [], [], [], [], [], [] + + # add residual + for bucket_i, bucket_frames in enumerate(batch_accum): + if bucket_frames > 0: + prompts_all.append( + ( + utts[bucket_i], + ref_rms_list[bucket_i], + padded_mel_batch(ref_mels[bucket_i]), + ref_mel_lens[bucket_i], + total_mel_lens[bucket_i], + final_text_list[bucket_i], + ) + ) + # not only leave easy work for last workers + random.seed(666) + random.shuffle(prompts_all) + + return prompts_all + + +# get wav_res_ref_text of seed-tts test metalst +# https://github.com/BytedanceSpeech/seed-tts-eval + + +def get_seed_tts_test(metalst, gen_wav_dir, gpus): + f = open(metalst) + lines = f.readlines() + f.close() + + test_set_ = [] + for line in tqdm(lines): + if len(line.strip().split("|")) == 5: + utt, prompt_text, prompt_wav, gt_text, gt_wav = line.strip().split("|") + elif len(line.strip().split("|")) == 4: + utt, prompt_text, prompt_wav, gt_text = line.strip().split("|") + + if not os.path.exists(os.path.join(gen_wav_dir, utt + ".wav")): + continue + gen_wav = os.path.join(gen_wav_dir, utt + ".wav") + if not os.path.isabs(prompt_wav): + prompt_wav = os.path.join(os.path.dirname(metalst), prompt_wav) + + test_set_.append((gen_wav, prompt_wav, gt_text)) + + num_jobs = len(gpus) + if num_jobs == 1: + return [(gpus[0], test_set_)] + + wav_per_job = len(test_set_) // num_jobs + 1 + test_set = [] + for i in range(num_jobs): + test_set.append((gpus[i], test_set_[i * wav_per_job : (i + 1) * wav_per_job])) + + return test_set + + +# get librispeech test-clean cross sentence test + + +def get_librispeech_test(metalst, gen_wav_dir, gpus, librispeech_test_clean_path, eval_ground_truth=False): + f = open(metalst) + lines = f.readlines() + f.close() + + test_set_ = [] + for line in tqdm(lines): + ref_utt, ref_dur, ref_txt, gen_utt, gen_dur, gen_txt = line.strip().split("\t") + + if eval_ground_truth: + gen_spk_id, gen_chaptr_id, _ = gen_utt.split("-") + gen_wav = os.path.join(librispeech_test_clean_path, gen_spk_id, gen_chaptr_id, gen_utt + ".flac") + else: + if not os.path.exists(os.path.join(gen_wav_dir, gen_utt + ".wav")): + raise FileNotFoundError(f"Generated wav not found: {gen_utt}") + gen_wav = os.path.join(gen_wav_dir, gen_utt + ".wav") + + ref_spk_id, ref_chaptr_id, _ = ref_utt.split("-") + ref_wav = os.path.join(librispeech_test_clean_path, ref_spk_id, ref_chaptr_id, ref_utt + ".flac") + + test_set_.append((gen_wav, ref_wav, gen_txt)) + + num_jobs = len(gpus) + if num_jobs == 1: + return [(gpus[0], test_set_)] + + wav_per_job = len(test_set_) // num_jobs + 1 + test_set = [] + for i in range(num_jobs): + test_set.append((gpus[i], test_set_[i * wav_per_job : (i + 1) * wav_per_job])) + + return test_set + + +# load asr model + + +def load_asr_model(lang, ckpt_dir=""): + if lang == "zh": + from funasr import AutoModel + + model = AutoModel( + model=os.path.join(ckpt_dir, "paraformer-zh"), + # vad_model = os.path.join(ckpt_dir, "fsmn-vad"), + # punc_model = os.path.join(ckpt_dir, "ct-punc"), + # spk_model = os.path.join(ckpt_dir, "cam++"), + disable_update=True, + ) # following seed-tts setting + elif lang == "en": + from faster_whisper import WhisperModel + + model_size = "large-v3" if ckpt_dir == "" else ckpt_dir + model = WhisperModel(model_size, device="cuda", compute_type="float16") + return model + + +# WER Evaluation, the way Seed-TTS does + + +def run_asr_wer(args): + rank, lang, test_set, ckpt_dir = args + + if lang == "zh": + import zhconv + + torch.cuda.set_device(rank) + elif lang == "en": + os.environ["CUDA_VISIBLE_DEVICES"] = str(rank) + else: + raise NotImplementedError( + "lang support only 'zh' (funasr paraformer-zh), 'en' (faster-whisper-large-v3), for now." + ) + + asr_model = load_asr_model(lang, ckpt_dir=ckpt_dir) + + from zhon.hanzi import punctuation + + punctuation_all = punctuation + string.punctuation + wer_results = [] + + from jiwer import compute_measures + + for gen_wav, prompt_wav, truth in tqdm(test_set): + if lang == "zh": + res = asr_model.generate(input=gen_wav, batch_size_s=300, disable_pbar=True) + hypo = res[0]["text"] + hypo = zhconv.convert(hypo, "zh-cn") + elif lang == "en": + segments, _ = asr_model.transcribe(gen_wav, beam_size=5, language="en") + hypo = "" + for segment in segments: + hypo = hypo + " " + segment.text + + raw_truth = truth + raw_hypo = hypo + + for x in punctuation_all: + truth = truth.replace(x, "") + hypo = hypo.replace(x, "") + + truth = truth.replace(" ", " ") + hypo = hypo.replace(" ", " ") + + if lang == "zh": + truth = " ".join([x for x in truth]) + hypo = " ".join([x for x in hypo]) + elif lang == "en": + truth = truth.lower() + hypo = hypo.lower() + + measures = compute_measures(truth, hypo) + wer = measures["wer"] + + # ref_list = truth.split(" ") + # subs = measures["substitutions"] / len(ref_list) + # dele = measures["deletions"] / len(ref_list) + # inse = measures["insertions"] / len(ref_list) + + wer_results.append( + { + "wav": Path(gen_wav).stem, + "truth": raw_truth, + "hypo": raw_hypo, + "wer": wer, + } + ) + + return wer_results + + +# SIM Evaluation + + +def run_sim(args): + rank, test_set, ckpt_dir = args + device = f"cuda:{rank}" + + model = ECAPA_TDNN_SMALL(feat_dim=1024, feat_type="wavlm_large", config_path=None) + state_dict = torch.load(ckpt_dir, weights_only=True, map_location=lambda storage, loc: storage) + model.load_state_dict(state_dict["model"], strict=False) + + use_gpu = True if torch.cuda.is_available() else False + if use_gpu: + model = model.cuda(device) + model.eval() + + sim_results = [] + for gen_wav, prompt_wav, truth in tqdm(test_set): + wav1, sr1 = torchaudio.load(gen_wav) + wav2, sr2 = torchaudio.load(prompt_wav) + + resample1 = torchaudio.transforms.Resample(orig_freq=sr1, new_freq=16000) + resample2 = torchaudio.transforms.Resample(orig_freq=sr2, new_freq=16000) + wav1 = resample1(wav1) + wav2 = resample2(wav2) + + if use_gpu: + wav1 = wav1.cuda(device) + wav2 = wav2.cuda(device) + with torch.no_grad(): + emb1 = model(wav1) + emb2 = model(wav2) + + sim = F.cosine_similarity(emb1, emb2)[0].item() + # print(f"VSim score between two audios: {sim:.4f} (-1.0, 1.0).") + sim_results.append( + { + "wav": Path(gen_wav).stem, + "sim": sim, + } + ) + + return sim_results diff --git a/src/f5_tts/src/f5_tts/infer/README.md b/src/f5_tts/src/f5_tts/infer/README.md new file mode 100644 index 0000000000000000000000000000000000000000..87346caf94d41d22539a6f721d00bd1652facf18 --- /dev/null +++ b/src/f5_tts/src/f5_tts/infer/README.md @@ -0,0 +1,177 @@ +# Inference + +The pretrained model checkpoints can be reached at [🤗 Hugging Face](https://huggingface.co/SWivid/F5-TTS) and [🤖 Model Scope](https://www.modelscope.cn/models/SWivid/F5-TTS_Emilia-ZH-EN), or will be automatically downloaded when running inference scripts. + +**More checkpoints with whole community efforts can be found in [SHARED.md](SHARED.md), supporting more languages.** + +Currently support **30s for a single** generation, which is the **total length** (same logic if `fix_duration`) including both prompt and output audio. However, `infer_cli` and `infer_gradio` will automatically do chunk generation for longer text. Long reference audio will be **clip short to ~12s**. + +To avoid possible inference failures, make sure you have seen through the following instructions. + +- Use reference audio <12s and leave proper silence space (e.g. 1s) at the end. Otherwise there is a risk of truncating in the middle of word, leading to suboptimal generation. +- Uppercased letters (best with form like K.F.C.) will be uttered letter by letter, and lowercased letters used for common words. +- Add some spaces (blank: " ") or punctuations (e.g. "," ".") to explicitly introduce some pauses. +- If English punctuation marks the end of a sentence, make sure there is a space " " after it. Otherwise not regarded as when chunk. +- Preprocess numbers to Chinese letters if you want to have them read in Chinese, otherwise in English. +- If the generation output is blank (pure silence), check for FFmpeg installation. +- Try turn off `use_ema` if using an early-stage finetuned checkpoint (which goes just few updates). + + +## Gradio App + +Currently supported features: + +- Basic TTS with Chunk Inference +- Multi-Style / Multi-Speaker Generation +- Voice Chat powered by Qwen2.5-3B-Instruct +- [Custom inference with more language support](SHARED.md) + +The cli command `f5-tts_infer-gradio` equals to `python src/f5_tts/infer/infer_gradio.py`, which launches a Gradio APP (web interface) for inference. + +The script will load model checkpoints from Huggingface. You can also manually download files and update the path to `load_model()` in `infer_gradio.py`. Currently only load TTS models first, will load ASR model to do transcription if `ref_text` not provided, will load LLM model if use Voice Chat. + +More flags options: + +```bash +# Automatically launch the interface in the default web browser +f5-tts_infer-gradio --inbrowser + +# Set the root path of the application, if it's not served from the root ("/") of the domain +# For example, if the application is served at "https://example.com/myapp" +f5-tts_infer-gradio --root_path "/myapp" +``` + +Could also be used as a component for larger application: +```python +import gradio as gr +from f5_tts.infer.infer_gradio import app + +with gr.Blocks() as main_app: + gr.Markdown("# This is an example of using F5-TTS within a bigger Gradio app") + + # ... other Gradio components + + app.render() + +main_app.launch() +``` + + +## CLI Inference + +The cli command `f5-tts_infer-cli` equals to `python src/f5_tts/infer/infer_cli.py`, which is a command line tool for inference. + +The script will load model checkpoints from Huggingface. You can also manually download files and use `--ckpt_file` to specify the model you want to load, or directly update in `infer_cli.py`. + +For change vocab.txt use `--vocab_file` to provide your `vocab.txt` file. + +Basically you can inference with flags: +```bash +# Leave --ref_text "" will have ASR model transcribe (extra GPU memory usage) +f5-tts_infer-cli \ +--model F5TTS_v1_Base \ +--ref_audio "ref_audio.wav" \ +--ref_text "The content, subtitle or transcription of reference audio." \ +--gen_text "Some text you want TTS model generate for you." + +# Use BigVGAN as vocoder. Currently only support F5TTS_Base. +f5-tts_infer-cli --model F5TTS_Base --vocoder_name bigvgan --load_vocoder_from_local + +# Use custom path checkpoint, e.g. +f5-tts_infer-cli --ckpt_file ckpts/F5TTS_v1_Base/model_1250000.safetensors + +# More instructions +f5-tts_infer-cli --help +``` + +And a `.toml` file would help with more flexible usage. + +```bash +f5-tts_infer-cli -c custom.toml +``` + +For example, you can use `.toml` to pass in variables, refer to `src/f5_tts/infer/examples/basic/basic.toml`: + +```toml +# F5TTS_v1_Base | E2TTS_Base +model = "F5TTS_v1_Base" +ref_audio = "infer/examples/basic/basic_ref_en.wav" +# If an empty "", transcribes the reference audio automatically. +ref_text = "Some call me nature, others call me mother nature." +gen_text = "I don't really care what you call me. I've been a silent spectator, watching species evolve, empires rise and fall. But always remember, I am mighty and enduring." +# File with text to generate. Ignores the text above. +gen_file = "" +remove_silence = false +output_dir = "tests" +``` + +You can also leverage `.toml` file to do multi-style generation, refer to `src/f5_tts/infer/examples/multi/story.toml`. + +```toml +# F5TTS_v1_Base | E2TTS_Base +model = "F5TTS_v1_Base" +ref_audio = "infer/examples/multi/main.flac" +# If an empty "", transcribes the reference audio automatically. +ref_text = "" +gen_text = "" +# File with text to generate. Ignores the text above. +gen_file = "infer/examples/multi/story.txt" +remove_silence = true +output_dir = "tests" + +[voices.town] +ref_audio = "infer/examples/multi/town.flac" +ref_text = "" + +[voices.country] +ref_audio = "infer/examples/multi/country.flac" +ref_text = "" +``` +You should mark the voice with `[main]` `[town]` `[country]` whenever you want to change voice, refer to `src/f5_tts/infer/examples/multi/story.txt`. + +## API Usage + +```python +from importlib.resources import files +from f5_tts.api import F5TTS + +f5tts = F5TTS() +wav, sr, spec = f5tts.infer( + ref_file=str(files("f5_tts").joinpath("infer/examples/basic/basic_ref_en.wav")), + ref_text="some call me nature, others call me mother nature.", + gen_text="""I don't really care what you call me. I've been a silent spectator, watching species evolve, empires rise and fall. But always remember, I am mighty and enduring. Respect me and I'll nurture you; ignore me and you shall face the consequences.""", + file_wave=str(files("f5_tts").joinpath("../../tests/api_out.wav")), + file_spec=str(files("f5_tts").joinpath("../../tests/api_out.png")), + seed=None, +) +``` +Check [api.py](../api.py) for more details. + +## TensorRT-LLM Deployment + +See [detailed instructions](../runtime/triton_trtllm/README.md) for more information. + +## Socket Real-time Service + +Real-time voice output with chunk stream: + +```bash +# Start socket server +python src/f5_tts/socket_server.py + +# If PyAudio not installed +sudo apt-get install portaudio19-dev +pip install pyaudio + +# Communicate with socket client +python src/f5_tts/socket_client.py +``` + +## Speech Editing + +To test speech editing capabilities, use the following command: + +```bash +python src/f5_tts/infer/speech_edit.py +``` + diff --git a/src/f5_tts/src/f5_tts/infer/SHARED.md b/src/f5_tts/src/f5_tts/infer/SHARED.md new file mode 100644 index 0000000000000000000000000000000000000000..27b612488b466d72131ac7763d250fbd851b2b64 --- /dev/null +++ b/src/f5_tts/src/f5_tts/infer/SHARED.md @@ -0,0 +1,193 @@ + +# Shared Model Cards + + +### **Prerequisites of using** +- This document is serving as a quick lookup table for the community training/finetuning result, with various language support. +- The models in this repository are open source and are based on voluntary contributions from contributors. +- The use of models must be conditioned on respect for the respective creators. The convenience brought comes from their efforts. + + +### **Welcome to share here** +- Have a pretrained/finetuned result: model checkpoint (pruned best to facilitate inference, i.e. leave only `ema_model_state_dict`) and corresponding vocab file (for tokenization). +- Host a public [huggingface model repository](https://huggingface.co/new) and upload the model related files. +- Make a pull request adding a model card to the current page, i.e. `src\f5_tts\infer\SHARED.md`. + + +### Supported Languages +- [Multilingual](#multilingual) + - [F5-TTS v1 v0 Base @ zh \& en @ F5-TTS](#f5-tts-v1-v0-base--zh--en--f5-tts) +- [English](#english) +- [Finnish](#finnish) + - [F5-TTS Base @ fi @ AsmoKoskinen](#f5-tts-base--fi--asmokoskinen) +- [French](#french) + - [F5-TTS Base @ fr @ RASPIAUDIO](#f5-tts-base--fr--raspiaudio) +- [German](#german) + - [F5-TTS Base @ de @ hvoss-techfak](#f5-tts-base--de--hvoss-techfak) +- [Hindi](#hindi) + - [F5-TTS Small @ hi @ SPRINGLab](#f5-tts-small--hi--springlab) +- [Italian](#italian) + - [F5-TTS Base @ it @ alien79](#f5-tts-base--it--alien79) +- [Japanese](#japanese) + - [F5-TTS Base @ ja @ Jmica](#f5-tts-base--ja--jmica) +- [Mandarin](#mandarin) +- [Russian](#russian) + - [F5-TTS Base @ ru @ HotDro4illa](#f5-tts-base--ru--hotdro4illa) +- [Spanish](#spanish) + - [F5-TTS Base @ es @ jpgallegoar](#f5-tts-base--es--jpgallegoar) + + +## Multilingual + +#### F5-TTS v1 v0 Base @ zh & en @ F5-TTS +|Model|🤗Hugging Face|Data (Hours)|Model License| +|:---:|:------------:|:-----------:|:-------------:| +|F5-TTS v1 Base|[ckpt & vocab](https://huggingface.co/SWivid/F5-TTS/tree/main/F5TTS_v1_Base)|[Emilia 95K zh&en](https://huggingface.co/datasets/amphion/Emilia-Dataset/tree/fc71e07)|cc-by-nc-4.0| + +```bash +Model: hf://SWivid/F5-TTS/F5TTS_v1_Base/model_1250000.safetensors +# A Variant Model: hf://SWivid/F5-TTS/F5TTS_v1_Base_no_zero_init/model_1250000.safetensors +Vocab: hf://SWivid/F5-TTS/F5TTS_v1_Base/vocab.txt +Config: {"dim": 1024, "depth": 22, "heads": 16, "ff_mult": 2, "text_dim": 512, "conv_layers": 4} +``` + +|Model|🤗Hugging Face|Data (Hours)|Model License| +|:---:|:------------:|:-----------:|:-------------:| +|F5-TTS Base|[ckpt & vocab](https://huggingface.co/SWivid/F5-TTS/tree/main/F5TTS_Base)|[Emilia 95K zh&en](https://huggingface.co/datasets/amphion/Emilia-Dataset/tree/fc71e07)|cc-by-nc-4.0| + +```bash +Model: hf://SWivid/F5-TTS/F5TTS_Base/model_1200000.safetensors +Vocab: hf://SWivid/F5-TTS/F5TTS_Base/vocab.txt +Config: {"dim": 1024, "depth": 22, "heads": 16, "ff_mult": 2, "text_dim": 512, "text_mask_padding": False, "conv_layers": 4, "pe_attn_head": 1} +``` + +*Other infos, e.g. Author info, Github repo, Link to some sampled results, Usage instruction, Tutorial (Blog, Video, etc.) ...* + + +## English + + +## Finnish + +#### F5-TTS Base @ fi @ AsmoKoskinen +|Model|🤗Hugging Face|Data|Model License| +|:---:|:------------:|:-----------:|:-------------:| +|F5-TTS Base|[ckpt & vocab](https://huggingface.co/AsmoKoskinen/F5-TTS_Finnish_Model)|[Common Voice](https://huggingface.co/datasets/mozilla-foundation/common_voice_17_0), [Vox Populi](https://huggingface.co/datasets/facebook/voxpopuli)|cc-by-nc-4.0| + +```bash +Model: hf://AsmoKoskinen/F5-TTS_Finnish_Model/model_common_voice_fi_vox_populi_fi_20241206.safetensors +Vocab: hf://AsmoKoskinen/F5-TTS_Finnish_Model/vocab.txt +Config: {"dim": 1024, "depth": 22, "heads": 16, "ff_mult": 2, "text_dim": 512, "text_mask_padding": False, "conv_layers": 4, "pe_attn_head": 1} +``` + + +## French + +#### F5-TTS Base @ fr @ RASPIAUDIO +|Model|🤗Hugging Face|Data (Hours)|Model License| +|:---:|:------------:|:-----------:|:-------------:| +|F5-TTS Base|[ckpt & vocab](https://huggingface.co/RASPIAUDIO/F5-French-MixedSpeakers-reduced)|[LibriVox](https://librivox.org/)|cc-by-nc-4.0| + +```bash +Model: hf://RASPIAUDIO/F5-French-MixedSpeakers-reduced/model_last_reduced.pt +Vocab: hf://RASPIAUDIO/F5-French-MixedSpeakers-reduced/vocab.txt +Config: {"dim": 1024, "depth": 22, "heads": 16, "ff_mult": 2, "text_dim": 512, "text_mask_padding": False, "conv_layers": 4, "pe_attn_head": 1} +``` + +- [Online Inference with Hugging Face Space](https://huggingface.co/spaces/RASPIAUDIO/f5-tts_french). +- [Tutorial video to train a new language model](https://www.youtube.com/watch?v=UO4usaOojys). +- [Discussion about this training can be found here](https://github.com/SWivid/F5-TTS/issues/434). + + +## German + +#### F5-TTS Base @ de @ hvoss-techfak +|Model|🤗Hugging Face|Data (Hours)|Model License| +|:---:|:------------:|:-----------:|:-------------:| +|F5-TTS Base|[ckpt & vocab](https://huggingface.co/hvoss-techfak/F5-TTS-German)|[Mozilla Common Voice 19.0](https://commonvoice.mozilla.org/en/datasets) & 800 hours Crowdsourced |cc-by-nc-4.0| + +```bash +Model: hf://hvoss-techfak/F5-TTS-German/model_f5tts_german.pt +Vocab: hf://hvoss-techfak/F5-TTS-German/vocab.txt +Config: {"dim": 1024, "depth": 22, "heads": 16, "ff_mult": 2, "text_dim": 512, "text_mask_padding": False, "conv_layers": 4, "pe_attn_head": 1} +``` + +- Finetuned by [@hvoss-techfak](https://github.com/hvoss-techfak) + + +## Hindi + +#### F5-TTS Small @ hi @ SPRINGLab +|Model|🤗Hugging Face|Data (Hours)|Model License| +|:---:|:------------:|:-----------:|:-------------:| +|F5-TTS Small|[ckpt & vocab](https://huggingface.co/SPRINGLab/F5-Hindi-24KHz)|[IndicTTS Hi](https://huggingface.co/datasets/SPRINGLab/IndicTTS-Hindi) & [IndicVoices-R Hi](https://huggingface.co/datasets/SPRINGLab/IndicVoices-R_Hindi) |cc-by-4.0| + +```bash +Model: hf://SPRINGLab/F5-Hindi-24KHz/model_2500000.safetensors +Vocab: hf://SPRINGLab/F5-Hindi-24KHz/vocab.txt +Config: {"dim": 768, "depth": 18, "heads": 12, "ff_mult": 2, "text_dim": 512, "text_mask_padding": False, "conv_layers": 4, "pe_attn_head": 1} +``` + +- Authors: SPRING Lab, Indian Institute of Technology, Madras +- Website: https://asr.iitm.ac.in/ + + +## Italian + +#### F5-TTS Base @ it @ alien79 +|Model|🤗Hugging Face|Data|Model License| +|:---:|:------------:|:-----------:|:-------------:| +|F5-TTS Base|[ckpt & vocab](https://huggingface.co/alien79/F5-TTS-italian)|[ylacombe/cml-tts](https://huggingface.co/datasets/ylacombe/cml-tts) |cc-by-nc-4.0| + +```bash +Model: hf://alien79/F5-TTS-italian/model_159600.safetensors +Vocab: hf://alien79/F5-TTS-italian/vocab.txt +Config: {"dim": 1024, "depth": 22, "heads": 16, "ff_mult": 2, "text_dim": 512, "text_mask_padding": False, "conv_layers": 4, "pe_attn_head": 1} +``` + +- Trained by [Mithril Man](https://github.com/MithrilMan) +- Model details on [hf project home](https://huggingface.co/alien79/F5-TTS-italian) +- Open to collaborations to further improve the model + + +## Japanese + +#### F5-TTS Base @ ja @ Jmica +|Model|🤗Hugging Face|Data (Hours)|Model License| +|:---:|:------------:|:-----------:|:-------------:| +|F5-TTS Base|[ckpt & vocab](https://huggingface.co/Jmica/F5TTS/tree/main/JA_21999120)|[Emilia 1.7k JA](https://huggingface.co/datasets/amphion/Emilia-Dataset/tree/fc71e07) & [Galgame Dataset 5.4k](https://huggingface.co/datasets/OOPPEENN/Galgame_Dataset)|cc-by-nc-4.0| + +```bash +Model: hf://Jmica/F5TTS/JA_21999120/model_21999120.pt +Vocab: hf://Jmica/F5TTS/JA_21999120/vocab_japanese.txt +Config: {"dim": 1024, "depth": 22, "heads": 16, "ff_mult": 2, "text_dim": 512, "text_mask_padding": False, "conv_layers": 4, "pe_attn_head": 1} +``` + + +## Mandarin + + +## Russian + +#### F5-TTS Base @ ru @ HotDro4illa +|Model|🤗Hugging Face|Data (Hours)|Model License| +|:---:|:------------:|:-----------:|:-------------:| +|F5-TTS Base|[ckpt & vocab](https://huggingface.co/hotstone228/F5-TTS-Russian)|[Common voice](https://huggingface.co/datasets/mozilla-foundation/common_voice_17_0)|cc-by-nc-4.0| + +```bash +Model: hf://hotstone228/F5-TTS-Russian/model_last.safetensors +Vocab: hf://hotstone228/F5-TTS-Russian/vocab.txt +Config: {"dim": 1024, "depth": 22, "heads": 16, "ff_mult": 2, "text_dim": 512, "text_mask_padding": False, "conv_layers": 4, "pe_attn_head": 1} +``` +- Finetuned by [HotDro4illa](https://github.com/HotDro4illa) +- Any improvements are welcome + + +## Spanish + +#### F5-TTS Base @ es @ jpgallegoar +|Model|🤗Hugging Face|Data (Hours)|Model License| +|:---:|:------------:|:-----------:|:-------------:| +|F5-TTS Base|[ckpt & vocab](https://huggingface.co/jpgallegoar/F5-Spanish)|[Voxpopuli](https://huggingface.co/datasets/facebook/voxpopuli) & Crowdsourced & TEDx, 218 hours|cc0-1.0| + +- @jpgallegoar [GitHub repo](https://github.com/jpgallegoar/Spanish-F5), Jupyter Notebook and Gradio usage for Spanish model. diff --git a/src/f5_tts/src/f5_tts/infer/examples/basic/basic.toml b/src/f5_tts/src/f5_tts/infer/examples/basic/basic.toml new file mode 100644 index 0000000000000000000000000000000000000000..b920b99776fd9e9ceee07af5e9ed3aa8ae0bc4f6 --- /dev/null +++ b/src/f5_tts/src/f5_tts/infer/examples/basic/basic.toml @@ -0,0 +1,11 @@ +# F5TTS_v1_Base | E2TTS_Base +model = "F5TTS_v1_Base" +ref_audio = "infer/examples/basic/basic_ref_en.wav" +# If an empty "", transcribes the reference audio automatically. +ref_text = "Some call me nature, others call me mother nature." +gen_text = "I don't really care what you call me. I've been a silent spectator, watching species evolve, empires rise and fall. But always remember, I am mighty and enduring." +# File with text to generate. Ignores the text above. +gen_file = "" +remove_silence = false +output_dir = "tests" +output_file = "infer_cli_basic.wav" diff --git a/src/f5_tts/src/f5_tts/infer/examples/basic/basic_ref_en.wav b/src/f5_tts/src/f5_tts/infer/examples/basic/basic_ref_en.wav new file mode 100644 index 0000000000000000000000000000000000000000..4d3a82d888e538146a312153645219dc7be64cc6 --- /dev/null +++ b/src/f5_tts/src/f5_tts/infer/examples/basic/basic_ref_en.wav @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b0e22048e72414fcc1e6b6342e47a774d748a195ed34e4a5b3fcf416707f2b71 +size 256018 diff --git a/src/f5_tts/src/f5_tts/infer/examples/basic/basic_ref_zh.wav b/src/f5_tts/src/f5_tts/infer/examples/basic/basic_ref_zh.wav new file mode 100644 index 0000000000000000000000000000000000000000..e270bf125183f336c18caab369713785dcc1e681 --- /dev/null +++ b/src/f5_tts/src/f5_tts/infer/examples/basic/basic_ref_zh.wav @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:96724a113240d1f82c6ded1334122f0176b96c9226ccd3c919e625bcfd2a3ede +size 324558 diff --git a/src/f5_tts/src/f5_tts/infer/examples/multi/country.flac b/src/f5_tts/src/f5_tts/infer/examples/multi/country.flac new file mode 100644 index 0000000000000000000000000000000000000000..fce14d9e97449466e185ad3ebc3ed5b918367787 --- /dev/null +++ b/src/f5_tts/src/f5_tts/infer/examples/multi/country.flac @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:bb15708b4b3875e37beec46591a5d89e1a9a63fdad3b8fe4a5c8738f4f554400 +size 180321 diff --git a/src/f5_tts/src/f5_tts/infer/examples/multi/main.flac b/src/f5_tts/src/f5_tts/infer/examples/multi/main.flac new file mode 100644 index 0000000000000000000000000000000000000000..912a0ed34a58d2bead35ce5863691d7c6a9f768f --- /dev/null +++ b/src/f5_tts/src/f5_tts/infer/examples/multi/main.flac @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4abb1107771ce7e14926fde879b959dde6db6e572476b98684f04e45e978ab19 +size 279219 diff --git a/src/f5_tts/src/f5_tts/infer/examples/multi/story.toml b/src/f5_tts/src/f5_tts/infer/examples/multi/story.toml new file mode 100644 index 0000000000000000000000000000000000000000..99a7648a960d3bb0ec2f9f0e0b24306403c70c62 --- /dev/null +++ b/src/f5_tts/src/f5_tts/infer/examples/multi/story.toml @@ -0,0 +1,20 @@ +# F5TTS_v1_Base | E2TTS_Base +model = "F5TTS_v1_Base" +ref_audio = "infer/examples/multi/main.flac" +# If an empty "", transcribes the reference audio automatically. +ref_text = "" +gen_text = "" +# File with text to generate. Ignores the text above. +gen_file = "infer/examples/multi/story.txt" +remove_silence = true +output_dir = "tests" +output_file = "infer_cli_story.wav" + +[voices.town] +ref_audio = "infer/examples/multi/town.flac" +ref_text = "" +speed = 0.8 # will ignore global speed + +[voices.country] +ref_audio = "infer/examples/multi/country.flac" +ref_text = "" diff --git a/src/f5_tts/src/f5_tts/infer/examples/multi/story.txt b/src/f5_tts/src/f5_tts/infer/examples/multi/story.txt new file mode 100644 index 0000000000000000000000000000000000000000..844e521f44b0cb75899e6c2ab23637d8d456b2d4 --- /dev/null +++ b/src/f5_tts/src/f5_tts/infer/examples/multi/story.txt @@ -0,0 +1 @@ +A Town Mouse and a Country Mouse were acquaintances, and the Country Mouse one day invited his friend to come and see him at his home in the fields. The Town Mouse came, and they sat down to a dinner of barleycorns and roots, the latter of which had a distinctly earthy flavour. The fare was not much to the taste of the guest, and presently he broke out with [town] "My poor dear friend, you live here no better than the ants! Now, you should just see how I fare! My larder is a regular horn of plenty. You must come and stay with me, and I promise you you shall live on the fat of the land." [main] So when he returned to town he took the Country Mouse with him, and showed him into a larder containing flour and oatmeal and figs and honey and dates. The Country Mouse had never seen anything like it, and sat down to enjoy the luxuries his friend provided: but before they had well begun, the door of the larder opened and someone came in. The two Mice scampered off and hid themselves in a narrow and exceedingly uncomfortable hole. Presently, when all was quiet, they ventured out again; but someone else came in, and off they scuttled again. This was too much for the visitor. [country] "Goodbye," [main] said he, [country] "I'm off. You live in the lap of luxury, I can see, but you are surrounded by dangers; whereas at home I can enjoy my simple dinner of roots and corn in peace." \ No newline at end of file diff --git a/src/f5_tts/src/f5_tts/infer/examples/multi/town.flac b/src/f5_tts/src/f5_tts/infer/examples/multi/town.flac new file mode 100644 index 0000000000000000000000000000000000000000..9f2df81dd2905da354da352dbdff52b2fdef88f5 --- /dev/null +++ b/src/f5_tts/src/f5_tts/infer/examples/multi/town.flac @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e7d069b8ebd5180c3b30fde5d378f0a1ddac96722d62cf43537efc3c3f3a3ce8 +size 229383 diff --git a/src/f5_tts/src/f5_tts/infer/examples/vocab.txt b/src/f5_tts/src/f5_tts/infer/examples/vocab.txt new file mode 100644 index 0000000000000000000000000000000000000000..cd934390e8f4b3ce98eb319ae618c084d01504b5 --- /dev/null +++ b/src/f5_tts/src/f5_tts/infer/examples/vocab.txt @@ -0,0 +1,2545 @@ + +! +" +# +$ +% +& +' +( +) +* ++ +, +- +. +/ +0 +1 +2 +3 +4 +5 +6 +7 +8 +9 +: +; += +> +? +@ +A +B +C +D +E +F +G +H +I +J +K +L +M +N +O +P +Q +R +S +T +U +V +W +X +Y +Z +[ +\ +] +_ +a +a1 +ai1 +ai2 +ai3 +ai4 +an1 +an3 +an4 +ang1 +ang2 +ang4 +ao1 +ao2 +ao3 +ao4 +b +ba +ba1 +ba2 +ba3 +ba4 +bai1 +bai2 +bai3 +bai4 +ban1 +ban2 +ban3 +ban4 +bang1 +bang2 +bang3 +bang4 +bao1 +bao2 +bao3 +bao4 +bei +bei1 +bei2 +bei3 +bei4 +ben1 +ben2 +ben3 +ben4 +beng +beng1 +beng2 +beng3 +beng4 +bi1 +bi2 +bi3 +bi4 +bian1 +bian2 +bian3 +bian4 +biao1 +biao2 +biao3 +bie1 +bie2 +bie3 +bie4 +bin1 +bin4 +bing1 +bing2 +bing3 +bing4 +bo +bo1 +bo2 +bo3 +bo4 +bu2 +bu3 +bu4 +c +ca1 +cai1 +cai2 +cai3 +cai4 +can1 +can2 +can3 +can4 +cang1 +cang2 +cao1 +cao2 +cao3 +ce4 +cen1 +cen2 +ceng1 +ceng2 +ceng4 +cha1 +cha2 +cha3 +cha4 +chai1 +chai2 +chan1 +chan2 +chan3 +chan4 +chang1 +chang2 +chang3 +chang4 +chao1 +chao2 +chao3 +che1 +che2 +che3 +che4 +chen1 +chen2 +chen3 +chen4 +cheng1 +cheng2 +cheng3 +cheng4 +chi1 +chi2 +chi3 +chi4 +chong1 +chong2 +chong3 +chong4 +chou1 +chou2 +chou3 +chou4 +chu1 +chu2 +chu3 +chu4 +chua1 +chuai1 +chuai2 +chuai3 +chuai4 +chuan1 +chuan2 +chuan3 +chuan4 +chuang1 +chuang2 +chuang3 +chuang4 +chui1 +chui2 +chun1 +chun2 +chun3 +chuo1 +chuo4 +ci1 +ci2 +ci3 +ci4 +cong1 +cong2 +cou4 +cu1 +cu4 +cuan1 +cuan2 +cuan4 +cui1 +cui3 +cui4 +cun1 +cun2 +cun4 +cuo1 +cuo2 +cuo4 +d +da +da1 +da2 +da3 +da4 +dai1 +dai2 +dai3 +dai4 +dan1 +dan2 +dan3 +dan4 +dang1 +dang2 +dang3 +dang4 +dao1 +dao2 +dao3 +dao4 +de +de1 +de2 +dei3 +den4 +deng1 +deng2 +deng3 +deng4 +di1 +di2 +di3 +di4 +dia3 +dian1 +dian2 +dian3 +dian4 +diao1 +diao3 +diao4 +die1 +die2 +die4 +ding1 +ding2 +ding3 +ding4 +diu1 +dong1 +dong3 +dong4 +dou1 +dou2 +dou3 +dou4 +du1 +du2 +du3 +du4 +duan1 +duan2 +duan3 +duan4 +dui1 +dui4 +dun1 +dun3 +dun4 +duo1 +duo2 +duo3 +duo4 +e +e1 +e2 +e3 +e4 +ei2 +en1 +en4 +er +er2 +er3 +er4 +f +fa1 +fa2 +fa3 +fa4 +fan1 +fan2 +fan3 +fan4 +fang1 +fang2 +fang3 +fang4 +fei1 +fei2 +fei3 +fei4 +fen1 +fen2 +fen3 +fen4 +feng1 +feng2 +feng3 +feng4 +fo2 +fou2 +fou3 +fu1 +fu2 +fu3 +fu4 +g +ga1 +ga2 +ga3 +ga4 +gai1 +gai2 +gai3 +gai4 +gan1 +gan2 +gan3 +gan4 +gang1 +gang2 +gang3 +gang4 +gao1 +gao2 +gao3 +gao4 +ge1 +ge2 +ge3 +ge4 +gei2 +gei3 +gen1 +gen2 +gen3 +gen4 +geng1 +geng3 +geng4 +gong1 +gong3 +gong4 +gou1 +gou2 +gou3 +gou4 +gu +gu1 +gu2 +gu3 +gu4 +gua1 +gua2 +gua3 +gua4 +guai1 +guai2 +guai3 +guai4 +guan1 +guan2 +guan3 +guan4 +guang1 +guang2 +guang3 +guang4 +gui1 +gui2 +gui3 +gui4 +gun3 +gun4 +guo1 +guo2 +guo3 +guo4 +h +ha1 +ha2 +ha3 +hai1 +hai2 +hai3 +hai4 +han1 +han2 +han3 +han4 +hang1 +hang2 +hang4 +hao1 +hao2 +hao3 +hao4 +he1 +he2 +he4 +hei1 +hen2 +hen3 +hen4 +heng1 +heng2 +heng4 +hong1 +hong2 +hong3 +hong4 +hou1 +hou2 +hou3 +hou4 +hu1 +hu2 +hu3 +hu4 +hua1 +hua2 +hua4 +huai2 +huai4 +huan1 +huan2 +huan3 +huan4 +huang1 +huang2 +huang3 +huang4 +hui1 +hui2 +hui3 +hui4 +hun1 +hun2 +hun4 +huo +huo1 +huo2 +huo3 +huo4 +i +j +ji1 +ji2 +ji3 +ji4 +jia +jia1 +jia2 +jia3 +jia4 +jian1 +jian2 +jian3 +jian4 +jiang1 +jiang2 +jiang3 +jiang4 +jiao1 +jiao2 +jiao3 +jiao4 +jie1 +jie2 +jie3 +jie4 +jin1 +jin2 +jin3 +jin4 +jing1 +jing2 +jing3 +jing4 +jiong3 +jiu1 +jiu2 +jiu3 +jiu4 +ju1 +ju2 +ju3 +ju4 +juan1 +juan2 +juan3 +juan4 +jue1 +jue2 +jue4 +jun1 +jun4 +k +ka1 +ka2 +ka3 +kai1 +kai2 +kai3 +kai4 +kan1 +kan2 +kan3 +kan4 +kang1 +kang2 +kang4 +kao1 +kao2 +kao3 +kao4 +ke1 +ke2 +ke3 +ke4 +ken3 +keng1 +kong1 +kong3 +kong4 +kou1 +kou2 +kou3 +kou4 +ku1 +ku2 +ku3 +ku4 +kua1 +kua3 +kua4 +kuai3 +kuai4 +kuan1 +kuan2 +kuan3 +kuang1 +kuang2 +kuang4 +kui1 +kui2 +kui3 +kui4 +kun1 +kun3 +kun4 +kuo4 +l +la +la1 +la2 +la3 +la4 +lai2 +lai4 +lan2 +lan3 +lan4 +lang1 +lang2 +lang3 +lang4 +lao1 +lao2 +lao3 +lao4 +le +le1 +le4 +lei +lei1 +lei2 +lei3 +lei4 +leng1 +leng2 +leng3 +leng4 +li +li1 +li2 +li3 +li4 +lia3 +lian2 +lian3 +lian4 +liang2 +liang3 +liang4 +liao1 +liao2 +liao3 +liao4 +lie1 +lie2 +lie3 +lie4 +lin1 +lin2 +lin3 +lin4 +ling2 +ling3 +ling4 +liu1 +liu2 +liu3 +liu4 +long1 +long2 +long3 +long4 +lou1 +lou2 +lou3 +lou4 +lu1 +lu2 +lu3 +lu4 +luan2 +luan3 +luan4 +lun1 +lun2 +lun4 +luo1 +luo2 +luo3 +luo4 +lv2 +lv3 +lv4 +lve3 +lve4 +m +ma +ma1 +ma2 +ma3 +ma4 +mai2 +mai3 +mai4 +man1 +man2 +man3 +man4 +mang2 +mang3 +mao1 +mao2 +mao3 +mao4 +me +mei2 +mei3 +mei4 +men +men1 +men2 +men4 +meng +meng1 +meng2 +meng3 +meng4 +mi1 +mi2 +mi3 +mi4 +mian2 +mian3 +mian4 +miao1 +miao2 +miao3 +miao4 +mie1 +mie4 +min2 +min3 +ming2 +ming3 +ming4 +miu4 +mo1 +mo2 +mo3 +mo4 +mou1 +mou2 +mou3 +mu2 +mu3 +mu4 +n +n2 +na1 +na2 +na3 +na4 +nai2 +nai3 +nai4 +nan1 +nan2 +nan3 +nan4 +nang1 +nang2 +nang3 +nao1 +nao2 +nao3 +nao4 +ne +ne2 +ne4 +nei3 +nei4 +nen4 +neng2 +ni1 +ni2 +ni3 +ni4 +nian1 +nian2 +nian3 +nian4 +niang2 +niang4 +niao2 +niao3 +niao4 +nie1 +nie4 +nin2 +ning2 +ning3 +ning4 +niu1 +niu2 +niu3 +niu4 +nong2 +nong4 +nou4 +nu2 +nu3 +nu4 +nuan3 +nuo2 +nuo4 +nv2 +nv3 +nve4 +o +o1 +o2 +ou1 +ou2 +ou3 +ou4 +p +pa1 +pa2 +pa4 +pai1 +pai2 +pai3 +pai4 +pan1 +pan2 +pan4 +pang1 +pang2 +pang4 +pao1 +pao2 +pao3 +pao4 +pei1 +pei2 +pei4 +pen1 +pen2 +pen4 +peng1 +peng2 +peng3 +peng4 +pi1 +pi2 +pi3 +pi4 +pian1 +pian2 +pian4 +piao1 +piao2 +piao3 +piao4 +pie1 +pie2 +pie3 +pin1 +pin2 +pin3 +pin4 +ping1 +ping2 +po1 +po2 +po3 +po4 +pou1 +pu1 +pu2 +pu3 +pu4 +q +qi1 +qi2 +qi3 +qi4 +qia1 +qia3 +qia4 +qian1 +qian2 +qian3 +qian4 +qiang1 +qiang2 +qiang3 +qiang4 +qiao1 +qiao2 +qiao3 +qiao4 +qie1 +qie2 +qie3 +qie4 +qin1 +qin2 +qin3 +qin4 +qing1 +qing2 +qing3 +qing4 +qiong1 +qiong2 +qiu1 +qiu2 +qiu3 +qu1 +qu2 +qu3 +qu4 +quan1 +quan2 +quan3 +quan4 +que1 +que2 +que4 +qun2 +r +ran2 +ran3 +rang1 +rang2 +rang3 +rang4 +rao2 +rao3 +rao4 +re2 +re3 +re4 +ren2 +ren3 +ren4 +reng1 +reng2 +ri4 +rong1 +rong2 +rong3 +rou2 +rou4 +ru2 +ru3 +ru4 +ruan2 +ruan3 +rui3 +rui4 +run4 +ruo4 +s +sa1 +sa2 +sa3 +sa4 +sai1 +sai4 +san1 +san2 +san3 +san4 +sang1 +sang3 +sang4 +sao1 +sao2 +sao3 +sao4 +se4 +sen1 +seng1 +sha1 +sha2 +sha3 +sha4 +shai1 +shai2 +shai3 +shai4 +shan1 +shan3 +shan4 +shang +shang1 +shang3 +shang4 +shao1 +shao2 +shao3 +shao4 +she1 +she2 +she3 +she4 +shei2 +shen1 +shen2 +shen3 +shen4 +sheng1 +sheng2 +sheng3 +sheng4 +shi +shi1 +shi2 +shi3 +shi4 +shou1 +shou2 +shou3 +shou4 +shu1 +shu2 +shu3 +shu4 +shua1 +shua2 +shua3 +shua4 +shuai1 +shuai3 +shuai4 +shuan1 +shuan4 +shuang1 +shuang3 +shui2 +shui3 +shui4 +shun3 +shun4 +shuo1 +shuo4 +si1 +si2 +si3 +si4 +song1 +song3 +song4 +sou1 +sou3 +sou4 +su1 +su2 +su4 +suan1 +suan4 +sui1 +sui2 +sui3 +sui4 +sun1 +sun3 +suo +suo1 +suo2 +suo3 +t +ta1 +ta2 +ta3 +ta4 +tai1 +tai2 +tai4 +tan1 +tan2 +tan3 +tan4 +tang1 +tang2 +tang3 +tang4 +tao1 +tao2 +tao3 +tao4 +te4 +teng2 +ti1 +ti2 +ti3 +ti4 +tian1 +tian2 +tian3 +tiao1 +tiao2 +tiao3 +tiao4 +tie1 +tie2 +tie3 +tie4 +ting1 +ting2 +ting3 +tong1 +tong2 +tong3 +tong4 +tou +tou1 +tou2 +tou4 +tu1 +tu2 +tu3 +tu4 +tuan1 +tuan2 +tui1 +tui2 +tui3 +tui4 +tun1 +tun2 +tun4 +tuo1 +tuo2 +tuo3 +tuo4 +u +v +w +wa +wa1 +wa2 +wa3 +wa4 +wai1 +wai3 +wai4 +wan1 +wan2 +wan3 +wan4 +wang1 +wang2 +wang3 +wang4 +wei1 +wei2 +wei3 +wei4 +wen1 +wen2 +wen3 +wen4 +weng1 +weng4 +wo1 +wo2 +wo3 +wo4 +wu1 +wu2 +wu3 +wu4 +x +xi1 +xi2 +xi3 +xi4 +xia1 +xia2 +xia4 +xian1 +xian2 +xian3 +xian4 +xiang1 +xiang2 +xiang3 +xiang4 +xiao1 +xiao2 +xiao3 +xiao4 +xie1 +xie2 +xie3 +xie4 +xin1 +xin2 +xin4 +xing1 +xing2 +xing3 +xing4 +xiong1 +xiong2 +xiu1 +xiu3 +xiu4 +xu +xu1 +xu2 +xu3 +xu4 +xuan1 +xuan2 +xuan3 +xuan4 +xue1 +xue2 +xue3 +xue4 +xun1 +xun2 +xun4 +y +ya +ya1 +ya2 +ya3 +ya4 +yan1 +yan2 +yan3 +yan4 +yang1 +yang2 +yang3 +yang4 +yao1 +yao2 +yao3 +yao4 +ye1 +ye2 +ye3 +ye4 +yi +yi1 +yi2 +yi3 +yi4 +yin1 +yin2 +yin3 +yin4 +ying1 +ying2 +ying3 +ying4 +yo1 +yong1 +yong2 +yong3 +yong4 +you1 +you2 +you3 +you4 +yu1 +yu2 +yu3 +yu4 +yuan1 +yuan2 +yuan3 +yuan4 +yue1 +yue4 +yun1 +yun2 +yun3 +yun4 +z +za1 +za2 +za3 +zai1 +zai3 +zai4 +zan1 +zan2 +zan3 +zan4 +zang1 +zang4 +zao1 +zao2 +zao3 +zao4 +ze2 +ze4 +zei2 +zen3 +zeng1 +zeng4 +zha1 +zha2 +zha3 +zha4 +zhai1 +zhai2 +zhai3 +zhai4 +zhan1 +zhan2 +zhan3 +zhan4 +zhang1 +zhang2 +zhang3 +zhang4 +zhao1 +zhao2 +zhao3 +zhao4 +zhe +zhe1 +zhe2 +zhe3 +zhe4 +zhen1 +zhen2 +zhen3 +zhen4 +zheng1 +zheng2 +zheng3 +zheng4 +zhi1 +zhi2 +zhi3 +zhi4 +zhong1 +zhong2 +zhong3 +zhong4 +zhou1 +zhou2 +zhou3 +zhou4 +zhu1 +zhu2 +zhu3 +zhu4 +zhua1 +zhua2 +zhua3 +zhuai1 +zhuai3 +zhuai4 +zhuan1 +zhuan2 +zhuan3 +zhuan4 +zhuang1 +zhuang4 +zhui1 +zhui4 +zhun1 +zhun2 +zhun3 +zhuo1 +zhuo2 +zi +zi1 +zi2 +zi3 +zi4 +zong1 +zong2 +zong3 +zong4 +zou1 +zou2 +zou3 +zou4 +zu1 +zu2 +zu3 +zuan1 +zuan3 +zuan4 +zui2 +zui3 +zui4 +zun1 +zuo +zuo1 +zuo2 +zuo3 +zuo4 +{ +~ +¡ +¢ +£ +¥ +§ +¨ +© +« +® +¯ +° +± +² +³ +´ +µ +· +¹ +º +» +¼ +½ +¾ +¿ +À +Á + +à +Ä +Å +Æ +Ç +È +É +Ê +Í +Î +Ñ +Ó +Ö +× +Ø +Ú +Ü +Ý +Þ +ß +à +á +â +ã +ä +å +æ +ç +è +é +ê +ë +ì +í +î +ï +ð +ñ +ò +ó +ô +õ +ö +ø +ù +ú +û +ü +ý +Ā +ā +ă +ą +ć +Č +č +Đ +đ +ē +ė +ę +ě +ĝ +ğ +ħ +ī +į +İ +ı +Ł +ł +ń +ņ +ň +ŋ +Ō +ō +ő +œ +ř +Ś +ś +Ş +ş +Š +š +Ť +ť +ũ +ū +ź +Ż +ż +Ž +ž +ơ +ư +ǎ +ǐ +ǒ +ǔ +ǚ +ș +ț +ɑ +ɔ +ɕ +ə +ɛ +ɜ +ɡ +ɣ +ɪ +ɫ +ɴ +ɹ +ɾ +ʃ +ʊ +ʌ +ʒ +ʔ +ʰ +ʷ +ʻ +ʾ +ʿ +ˈ +ː +˙ +˜ +ˢ +́ +̅ +Α +Β +Δ +Ε +Θ +Κ +Λ +Μ +Ξ +Π +Σ +Τ +Φ +Χ +Ψ +Ω +ά +έ +ή +ί +α +β +γ +δ +ε +ζ +η +θ +ι +κ +λ +μ +ν +ξ +ο +π +ρ +ς +σ +τ +υ +φ +χ +ψ +ω +ϊ +ό +ύ +ώ +ϕ +ϵ +Ё +А +Б +В +Г +Д +Е +Ж +З +И +Й +К +Л +М +Н +О +П +Р +С +Т +У +Ф +Х +Ц +Ч +Ш +Щ +Ы +Ь +Э +Ю +Я +а +б +в +г +д +е +ж +з +и +й +к +л +м +н +о +п +р +с +т +у +ф +х +ц +ч +ш +щ +ъ +ы +ь +э +ю +я +ё +і +ְ +ִ +ֵ +ֶ +ַ +ָ +ֹ +ּ +־ +ׁ +א +ב +ג +ד +ה +ו +ז +ח +ט +י +כ +ל +ם +מ +ן +נ +ס +ע +פ +ק +ר +ש +ת +أ +ب +ة +ت +ج +ح +د +ر +ز +س +ص +ط +ع +ق +ك +ل +م +ن +ه +و +ي +َ +ُ +ِ +ْ +ก +ข +ง +จ +ต +ท +น +ป +ย +ร +ว +ส +ห +อ +ฮ +ั +า +ี +ึ +โ +ใ +ไ +่ +้ +์ +ḍ +Ḥ +ḥ +ṁ +ṃ +ṅ +ṇ +Ṛ +ṛ +Ṣ +ṣ +Ṭ +ṭ +ạ +ả +Ấ +ấ +ầ +ậ +ắ +ằ +ẻ +ẽ +ế +ề +ể +ễ +ệ +ị +ọ +ỏ +ố +ồ +ộ +ớ +ờ +ở +ụ +ủ +ứ +ữ +ἀ +ἁ +Ἀ +ἐ +ἔ +ἰ +ἱ +ὀ +ὁ +ὐ +ὲ +ὸ +ᾶ +᾽ +ῆ +ῇ +ῶ +‎ +‑ +‒ +– +— +― +‖ +† +‡ +• +… +‧ +‬ +′ +″ +⁄ +⁡ +⁰ +⁴ +⁵ +⁶ +⁷ +⁸ +⁹ +₁ +₂ +₃ +€ +₱ +₹ +₽ +℃ +ℏ +ℓ +№ +ℝ +™ +⅓ +⅔ +⅛ +→ +∂ +∈ +∑ +− +∗ +√ +∞ +∫ +≈ +≠ +≡ +≤ +≥ +⋅ +⋯ +█ +♪ +⟨ +⟩ +、 +。 +《 +》 +「 +」 +【 +】 +あ +う +え +お +か +が +き +ぎ +く +ぐ +け +げ +こ +ご +さ +し +じ +す +ず +せ +ぜ +そ +ぞ +た +だ +ち +っ +つ +で +と +ど +な +に +ね +の +は +ば +ひ +ぶ +へ +べ +ま +み +む +め +も +ゃ +や +ゆ +ょ +よ +ら +り +る +れ +ろ +わ +を +ん +ァ +ア +ィ +イ +ウ +ェ +エ +オ +カ +ガ +キ +ク +ケ +ゲ +コ +ゴ +サ +ザ +シ +ジ +ス +ズ +セ +ゾ +タ +ダ +チ +ッ +ツ +テ +デ +ト +ド +ナ +ニ +ネ +ノ +バ +パ +ビ +ピ +フ +プ +ヘ +ベ +ペ +ホ +ボ +ポ +マ +ミ +ム +メ +モ +ャ +ヤ +ュ +ユ +ョ +ヨ +ラ +リ +ル +レ +ロ +ワ +ン +・ +ー +ㄋ +ㄍ +ㄎ +ㄏ +ㄓ +ㄕ +ㄚ +ㄜ +ㄟ +ㄤ +ㄥ +ㄧ +ㄱ +ㄴ +ㄷ +ㄹ +ㅁ +ㅂ +ㅅ +ㅈ +ㅍ +ㅎ +ㅏ +ㅓ +ㅗ +ㅜ +ㅡ +ㅣ +㗎 +가 +각 +간 +갈 +감 +갑 +갓 +갔 +강 +같 +개 +거 +건 +걸 +겁 +것 +겉 +게 +겠 +겨 +결 +겼 +경 +계 +고 +곤 +골 +곱 +공 +과 +관 +광 +교 +구 +국 +굴 +귀 +귄 +그 +근 +글 +금 +기 +긴 +길 +까 +깍 +깔 +깜 +깨 +께 +꼬 +꼭 +꽃 +꾸 +꿔 +끔 +끗 +끝 +끼 +나 +난 +날 +남 +납 +내 +냐 +냥 +너 +넘 +넣 +네 +녁 +년 +녕 +노 +녹 +놀 +누 +눈 +느 +는 +늘 +니 +님 +닙 +다 +닥 +단 +달 +닭 +당 +대 +더 +덕 +던 +덥 +데 +도 +독 +동 +돼 +됐 +되 +된 +될 +두 +둑 +둥 +드 +들 +등 +디 +따 +딱 +딸 +땅 +때 +떤 +떨 +떻 +또 +똑 +뚱 +뛰 +뜻 +띠 +라 +락 +란 +람 +랍 +랑 +래 +랜 +러 +런 +럼 +렇 +레 +려 +력 +렵 +렸 +로 +록 +롬 +루 +르 +른 +를 +름 +릉 +리 +릴 +림 +마 +막 +만 +많 +말 +맑 +맙 +맛 +매 +머 +먹 +멍 +메 +면 +명 +몇 +모 +목 +몸 +못 +무 +문 +물 +뭐 +뭘 +미 +민 +밌 +밑 +바 +박 +밖 +반 +받 +발 +밤 +밥 +방 +배 +백 +밸 +뱀 +버 +번 +벌 +벚 +베 +벼 +벽 +별 +병 +보 +복 +본 +볼 +봐 +봤 +부 +분 +불 +비 +빔 +빛 +빠 +빨 +뼈 +뽀 +뿅 +쁘 +사 +산 +살 +삼 +샀 +상 +새 +색 +생 +서 +선 +설 +섭 +섰 +성 +세 +셔 +션 +셨 +소 +속 +손 +송 +수 +숙 +순 +술 +숫 +숭 +숲 +쉬 +쉽 +스 +슨 +습 +슷 +시 +식 +신 +실 +싫 +심 +십 +싶 +싸 +써 +쓰 +쓴 +씌 +씨 +씩 +씬 +아 +악 +안 +않 +알 +야 +약 +얀 +양 +얘 +어 +언 +얼 +엄 +업 +없 +었 +엉 +에 +여 +역 +연 +염 +엽 +영 +옆 +예 +옛 +오 +온 +올 +옷 +옹 +와 +왔 +왜 +요 +욕 +용 +우 +운 +울 +웃 +워 +원 +월 +웠 +위 +윙 +유 +육 +윤 +으 +은 +을 +음 +응 +의 +이 +익 +인 +일 +읽 +임 +입 +있 +자 +작 +잔 +잖 +잘 +잡 +잤 +장 +재 +저 +전 +점 +정 +제 +져 +졌 +조 +족 +좀 +종 +좋 +죠 +주 +준 +줄 +중 +줘 +즈 +즐 +즘 +지 +진 +집 +짜 +짝 +쩌 +쪼 +쪽 +쫌 +쭈 +쯔 +찌 +찍 +차 +착 +찾 +책 +처 +천 +철 +체 +쳐 +쳤 +초 +촌 +추 +출 +춤 +춥 +춰 +치 +친 +칠 +침 +칩 +칼 +커 +켓 +코 +콩 +쿠 +퀴 +크 +큰 +큽 +키 +킨 +타 +태 +터 +턴 +털 +테 +토 +통 +투 +트 +특 +튼 +틀 +티 +팀 +파 +팔 +패 +페 +펜 +펭 +평 +포 +폭 +표 +품 +풍 +프 +플 +피 +필 +하 +학 +한 +할 +함 +합 +항 +해 +햇 +했 +행 +허 +험 +형 +혜 +호 +혼 +홀 +화 +회 +획 +후 +휴 +흐 +흔 +희 +히 +힘 +ﷺ +ﷻ +! +, +? +� +𠮶 diff --git a/src/f5_tts/src/f5_tts/infer/infer_cli.py b/src/f5_tts/src/f5_tts/infer/infer_cli.py new file mode 100644 index 0000000000000000000000000000000000000000..847b90e6c37469e3815f54214e6a5593127dc3c3 --- /dev/null +++ b/src/f5_tts/src/f5_tts/infer/infer_cli.py @@ -0,0 +1,383 @@ +import argparse +import codecs +import os +import re +from datetime import datetime +from importlib.resources import files +from pathlib import Path + +import numpy as np +import soundfile as sf +import tomli +from cached_path import cached_path +from hydra.utils import get_class +from omegaconf import OmegaConf +from unidecode import unidecode + +from f5_tts.infer.utils_infer import ( + cfg_strength, + cross_fade_duration, + device, + fix_duration, + infer_process, + load_model, + load_vocoder, + mel_spec_type, + nfe_step, + preprocess_ref_audio_text, + remove_silence_for_generated_wav, + speed, + sway_sampling_coef, + target_rms, +) + + +parser = argparse.ArgumentParser( + prog="python3 infer-cli.py", + description="Commandline interface for E2/F5 TTS with Advanced Batch Processing.", + epilog="Specify options above to override one or more settings from config.", +) +parser.add_argument( + "-c", + "--config", + type=str, + default=os.path.join(files("f5_tts").joinpath("infer/examples/basic"), "basic.toml"), + help="The configuration file, default see infer/examples/basic/basic.toml", +) + + +# Note. Not to provide default value here in order to read default from config file + +parser.add_argument( + "-m", + "--model", + type=str, + help="The model name: F5TTS_v1_Base | F5TTS_Base | E2TTS_Base | etc.", +) +parser.add_argument( + "-mc", + "--model_cfg", + type=str, + help="The path to F5-TTS model config file .yaml", +) +parser.add_argument( + "-p", + "--ckpt_file", + type=str, + help="The path to model checkpoint .pt, leave blank to use default", +) +parser.add_argument( + "-v", + "--vocab_file", + type=str, + help="The path to vocab file .txt, leave blank to use default", +) +parser.add_argument( + "-r", + "--ref_audio", + type=str, + help="The reference audio file.", +) +parser.add_argument( + "-s", + "--ref_text", + type=str, + help="The transcript/subtitle for the reference audio", +) +parser.add_argument( + "-t", + "--gen_text", + type=str, + help="The text to make model synthesize a speech", +) +parser.add_argument( + "-f", + "--gen_file", + type=str, + help="The file with text to generate, will ignore --gen_text", +) +parser.add_argument( + "-o", + "--output_dir", + type=str, + help="The path to output folder", +) +parser.add_argument( + "-w", + "--output_file", + type=str, + help="The name of output file", +) +parser.add_argument( + "--save_chunk", + action="store_true", + help="To save each audio chunks during inference", +) +parser.add_argument( + "--no_legacy_text", + action="store_false", + help="Not to use lossy ASCII transliterations of unicode text in saved file names.", +) +parser.add_argument( + "--remove_silence", + action="store_true", + help="To remove long silence found in ouput", +) +parser.add_argument( + "--load_vocoder_from_local", + action="store_true", + help="To load vocoder from local dir, default to ../checkpoints/vocos-mel-24khz", +) +parser.add_argument( + "--vocoder_name", + type=str, + choices=["vocos", "bigvgan"], + help=f"Used vocoder name: vocos | bigvgan, default {mel_spec_type}", +) +parser.add_argument( + "--target_rms", + type=float, + help=f"Target output speech loudness normalization value, default {target_rms}", +) +parser.add_argument( + "--cross_fade_duration", + type=float, + help=f"Duration of cross-fade between audio segments in seconds, default {cross_fade_duration}", +) +parser.add_argument( + "--nfe_step", + type=int, + help=f"The number of function evaluation (denoising steps), default {nfe_step}", +) +parser.add_argument( + "--cfg_strength", + type=float, + help=f"Classifier-free guidance strength, default {cfg_strength}", +) +parser.add_argument( + "--sway_sampling_coef", + type=float, + help=f"Sway Sampling coefficient, default {sway_sampling_coef}", +) +parser.add_argument( + "--speed", + type=float, + help=f"The speed of the generated audio, default {speed}", +) +parser.add_argument( + "--fix_duration", + type=float, + help=f"Fix the total duration (ref and gen audios) in seconds, default {fix_duration}", +) +parser.add_argument( + "--device", + type=str, + help="Specify the device to run on", +) +args = parser.parse_args() + + +# config file + +config = tomli.load(open(args.config, "rb")) + + +# command-line interface parameters + +model = args.model or config.get("model", "F5TTS_v1_Base") +ckpt_file = args.ckpt_file or config.get("ckpt_file", "") +vocab_file = args.vocab_file or config.get("vocab_file", "") + +ref_audio = args.ref_audio or config.get("ref_audio", "infer/examples/basic/basic_ref_en.wav") +ref_text = ( + args.ref_text + if args.ref_text is not None + else config.get("ref_text", "Some call me nature, others call me mother nature.") +) +gen_text = args.gen_text or config.get("gen_text", "Here we generate something just for test.") +gen_file = args.gen_file or config.get("gen_file", "") + +output_dir = args.output_dir or config.get("output_dir", "tests") +output_file = args.output_file or config.get( + "output_file", f"infer_cli_{datetime.now().strftime(r'%Y%m%d_%H%M%S')}.wav" +) + +save_chunk = args.save_chunk or config.get("save_chunk", False) +use_legacy_text = args.no_legacy_text or config.get("no_legacy_text", False) # no_legacy_text is a store_false arg +if save_chunk and use_legacy_text: + print( + "\nWarning to --save_chunk: lossy ASCII transliterations of unicode text for legacy (.wav) file names, --no_legacy_text to disable.\n" + ) + +remove_silence = args.remove_silence or config.get("remove_silence", False) +load_vocoder_from_local = args.load_vocoder_from_local or config.get("load_vocoder_from_local", False) + +vocoder_name = args.vocoder_name or config.get("vocoder_name", mel_spec_type) +target_rms = args.target_rms or config.get("target_rms", target_rms) +cross_fade_duration = args.cross_fade_duration or config.get("cross_fade_duration", cross_fade_duration) +nfe_step = args.nfe_step or config.get("nfe_step", nfe_step) +cfg_strength = args.cfg_strength or config.get("cfg_strength", cfg_strength) +sway_sampling_coef = args.sway_sampling_coef or config.get("sway_sampling_coef", sway_sampling_coef) +speed = args.speed or config.get("speed", speed) +fix_duration = args.fix_duration or config.get("fix_duration", fix_duration) +device = args.device or config.get("device", device) + + +# patches for pip pkg user +if "infer/examples/" in ref_audio: + ref_audio = str(files("f5_tts").joinpath(f"{ref_audio}")) +if "infer/examples/" in gen_file: + gen_file = str(files("f5_tts").joinpath(f"{gen_file}")) +if "voices" in config: + for voice in config["voices"]: + voice_ref_audio = config["voices"][voice]["ref_audio"] + if "infer/examples/" in voice_ref_audio: + config["voices"][voice]["ref_audio"] = str(files("f5_tts").joinpath(f"{voice_ref_audio}")) + + +# ignore gen_text if gen_file provided + +if gen_file: + gen_text = codecs.open(gen_file, "r", "utf-8").read() + + +# output path + +wave_path = Path(output_dir) / output_file +# spectrogram_path = Path(output_dir) / "infer_cli_out.png" +if save_chunk: + output_chunk_dir = os.path.join(output_dir, f"{Path(output_file).stem}_chunks") + if not os.path.exists(output_chunk_dir): + os.makedirs(output_chunk_dir) + + +# load vocoder + +if vocoder_name == "vocos": + vocoder_local_path = "../checkpoints/vocos-mel-24khz" +elif vocoder_name == "bigvgan": + vocoder_local_path = "../checkpoints/bigvgan_v2_24khz_100band_256x" + +vocoder = load_vocoder( + vocoder_name=vocoder_name, is_local=load_vocoder_from_local, local_path=vocoder_local_path, device=device +) + + +# load TTS model + +model_cfg = OmegaConf.load( + args.model_cfg or config.get("model_cfg", str(files("f5_tts").joinpath(f"configs/{model}.yaml"))) +) +model_cls = get_class(f"f5_tts.model.{model_cfg.model.backbone}") +model_arc = model_cfg.model.arch + +repo_name, ckpt_step, ckpt_type = "F5-TTS", 1250000, "safetensors" + +if model != "F5TTS_Base": + assert vocoder_name == model_cfg.model.mel_spec.mel_spec_type + +# override for previous models +if model == "F5TTS_Base": + if vocoder_name == "vocos": + ckpt_step = 1200000 + elif vocoder_name == "bigvgan": + model = "F5TTS_Base_bigvgan" + ckpt_type = "pt" +elif model == "E2TTS_Base": + repo_name = "E2-TTS" + ckpt_step = 1200000 + +if not ckpt_file: + ckpt_file = str(cached_path(f"hf://SWivid/{repo_name}/{model}/model_{ckpt_step}.{ckpt_type}")) + +print(f"Using {model}...") +ema_model = load_model( + model_cls, model_arc, ckpt_file, mel_spec_type=vocoder_name, vocab_file=vocab_file, device=device +) + + +# inference process + + +def main(): + main_voice = {"ref_audio": ref_audio, "ref_text": ref_text} + if "voices" not in config: + voices = {"main": main_voice} + else: + voices = config["voices"] + voices["main"] = main_voice + for voice in voices: + print("Voice:", voice) + print("ref_audio ", voices[voice]["ref_audio"]) + voices[voice]["ref_audio"], voices[voice]["ref_text"] = preprocess_ref_audio_text( + voices[voice]["ref_audio"], voices[voice]["ref_text"] + ) + print("ref_audio_", voices[voice]["ref_audio"], "\n\n") + + generated_audio_segments = [] + reg1 = r"(?=\[\w+\])" + chunks = re.split(reg1, gen_text) + reg2 = r"\[(\w+)\]" + for text in chunks: + if not text.strip(): + continue + match = re.match(reg2, text) + if match: + voice = match[1] + else: + print("No voice tag found, using main.") + voice = "main" + if voice not in voices: + print(f"Voice {voice} not found, using main.") + voice = "main" + text = re.sub(reg2, "", text) + ref_audio_ = voices[voice]["ref_audio"] + ref_text_ = voices[voice]["ref_text"] + local_speed = voices[voice].get("speed", speed) + gen_text_ = text.strip() + print(f"Voice: {voice}") + audio_segment, final_sample_rate, spectrogram = infer_process( + ref_audio_, + ref_text_, + gen_text_, + ema_model, + vocoder, + mel_spec_type=vocoder_name, + target_rms=target_rms, + cross_fade_duration=cross_fade_duration, + nfe_step=nfe_step, + cfg_strength=cfg_strength, + sway_sampling_coef=sway_sampling_coef, + speed=local_speed, + fix_duration=fix_duration, + device=device, + ) + generated_audio_segments.append(audio_segment) + + if save_chunk: + if len(gen_text_) > 200: + gen_text_ = gen_text_[:200] + " ... " + if use_legacy_text: + gen_text_ = unidecode(gen_text_) + sf.write( + os.path.join(output_chunk_dir, f"{len(generated_audio_segments) - 1}_{gen_text_}.wav"), + audio_segment, + final_sample_rate, + ) + + if generated_audio_segments: + final_wave = np.concatenate(generated_audio_segments) + + if not os.path.exists(output_dir): + os.makedirs(output_dir) + + with open(wave_path, "wb") as f: + sf.write(f.name, final_wave, final_sample_rate) + # Remove silence + if remove_silence: + remove_silence_for_generated_wav(f.name) + print(f.name) + + +if __name__ == "__main__": + main() diff --git a/src/f5_tts/src/f5_tts/infer/infer_gradio.py b/src/f5_tts/src/f5_tts/infer/infer_gradio.py new file mode 100644 index 0000000000000000000000000000000000000000..735dc45b54aa8b62b66899e34ae22c214d4883bc --- /dev/null +++ b/src/f5_tts/src/f5_tts/infer/infer_gradio.py @@ -0,0 +1,1121 @@ +# ruff: noqa: E402 +# Above allows ruff to ignore E402: module level import not at top of file + +import gc +import json +import os +import re +import tempfile +from collections import OrderedDict +from functools import lru_cache +from importlib.resources import files + +import click +import gradio as gr +import numpy as np +import soundfile as sf +import torch +import torchaudio +from cached_path import cached_path +from transformers import AutoModelForCausalLM, AutoTokenizer + + +try: + import spaces + + USING_SPACES = True +except ImportError: + USING_SPACES = False + + +def gpu_decorator(func): + if USING_SPACES: + return spaces.GPU(func) + else: + return func + + +from f5_tts.infer.utils_infer import ( + infer_process, + load_model, + load_vocoder, + preprocess_ref_audio_text, + remove_silence_for_generated_wav, + save_spectrogram, + tempfile_kwargs, +) +from f5_tts.model import DiT, UNetT + + +DEFAULT_TTS_MODEL = "F5-TTS_v1" +tts_model_choice = DEFAULT_TTS_MODEL + +DEFAULT_TTS_MODEL_CFG = [ + "hf://SWivid/F5-TTS/F5TTS_v1_Base/model_1250000.safetensors", + "hf://SWivid/F5-TTS/F5TTS_v1_Base/vocab.txt", + json.dumps(dict(dim=1024, depth=22, heads=16, ff_mult=2, text_dim=512, conv_layers=4)), +] + + +# load models + +vocoder = load_vocoder() + + +def load_f5tts(): + ckpt_path = str(cached_path(DEFAULT_TTS_MODEL_CFG[0])) + F5TTS_model_cfg = json.loads(DEFAULT_TTS_MODEL_CFG[2]) + return load_model(DiT, F5TTS_model_cfg, ckpt_path) + + +def load_e2tts(): + ckpt_path = str(cached_path("hf://SWivid/E2-TTS/E2TTS_Base/model_1200000.safetensors")) + E2TTS_model_cfg = dict(dim=1024, depth=24, heads=16, ff_mult=4, text_mask_padding=False, pe_attn_head=1) + return load_model(UNetT, E2TTS_model_cfg, ckpt_path) + + +def load_custom(ckpt_path: str, vocab_path="", model_cfg=None): + ckpt_path, vocab_path = ckpt_path.strip(), vocab_path.strip() + if ckpt_path.startswith("hf://"): + ckpt_path = str(cached_path(ckpt_path)) + if vocab_path.startswith("hf://"): + vocab_path = str(cached_path(vocab_path)) + if model_cfg is None: + model_cfg = json.loads(DEFAULT_TTS_MODEL_CFG[2]) + elif isinstance(model_cfg, str): + model_cfg = json.loads(model_cfg) + return load_model(DiT, model_cfg, ckpt_path, vocab_file=vocab_path) + + +F5TTS_ema_model = load_f5tts() +E2TTS_ema_model = load_e2tts() if USING_SPACES else None +custom_ema_model, pre_custom_path = None, "" + +chat_model_state = None +chat_tokenizer_state = None + + +@gpu_decorator +def chat_model_inference(messages, model, tokenizer): + """Generate response using Qwen""" + text = tokenizer.apply_chat_template( + messages, + tokenize=False, + add_generation_prompt=True, + ) + + model_inputs = tokenizer([text], return_tensors="pt").to(model.device) + generated_ids = model.generate( + **model_inputs, + max_new_tokens=512, + temperature=0.7, + top_p=0.95, + ) + + generated_ids = [ + output_ids[len(input_ids) :] for input_ids, output_ids in zip(model_inputs.input_ids, generated_ids) + ] + return tokenizer.batch_decode(generated_ids, skip_special_tokens=True)[0] + + +@gpu_decorator +def load_text_from_file(file): + if file: + with open(file, "r", encoding="utf-8") as f: + text = f.read().strip() + else: + text = "" + return gr.update(value=text) + + +@lru_cache(maxsize=1000) # NOTE. need to ensure params of infer() hashable +@gpu_decorator +def infer( + ref_audio_orig, + ref_text, + gen_text, + model, + remove_silence, + seed, + cross_fade_duration=0.15, + nfe_step=32, + speed=1, + show_info=gr.Info, +): + if not ref_audio_orig: + gr.Warning("Please provide reference audio.") + return gr.update(), gr.update(), ref_text + + # Set inference seed + if seed < 0 or seed > 2**31 - 1: + gr.Warning("Seed must in range 0 ~ 2147483647. Using random seed instead.") + seed = np.random.randint(0, 2**31 - 1) + torch.manual_seed(seed) + used_seed = seed + + if not gen_text.strip(): + gr.Warning("Please enter text to generate or upload a text file.") + return gr.update(), gr.update(), ref_text + + ref_audio, ref_text = preprocess_ref_audio_text(ref_audio_orig, ref_text, show_info=show_info) + + if model == DEFAULT_TTS_MODEL: + ema_model = F5TTS_ema_model + elif model == "E2-TTS": + global E2TTS_ema_model + if E2TTS_ema_model is None: + show_info("Loading E2-TTS model...") + E2TTS_ema_model = load_e2tts() + ema_model = E2TTS_ema_model + elif isinstance(model, tuple) and model[0] == "Custom": + assert not USING_SPACES, "Only official checkpoints allowed in Spaces." + global custom_ema_model, pre_custom_path + if pre_custom_path != model[1]: + show_info("Loading Custom TTS model...") + custom_ema_model = load_custom(model[1], vocab_path=model[2], model_cfg=model[3]) + pre_custom_path = model[1] + ema_model = custom_ema_model + + final_wave, final_sample_rate, combined_spectrogram = infer_process( + ref_audio, + ref_text, + gen_text, + ema_model, + vocoder, + cross_fade_duration=cross_fade_duration, + nfe_step=nfe_step, + speed=speed, + show_info=show_info, + progress=gr.Progress(), + ) + + # Remove silence + if remove_silence: + with tempfile.NamedTemporaryFile(suffix=".wav", **tempfile_kwargs) as f: + temp_path = f.name + try: + sf.write(temp_path, final_wave, final_sample_rate) + remove_silence_for_generated_wav(f.name) + final_wave, _ = torchaudio.load(f.name) + finally: + os.unlink(temp_path) + final_wave = final_wave.squeeze().cpu().numpy() + + # Save the spectrogram + with tempfile.NamedTemporaryFile(suffix=".png", **tempfile_kwargs) as tmp_spectrogram: + spectrogram_path = tmp_spectrogram.name + save_spectrogram(combined_spectrogram, spectrogram_path) + + return (final_sample_rate, final_wave), spectrogram_path, ref_text, used_seed + + +with gr.Blocks() as app_tts: + gr.Markdown("# Batched TTS") + ref_audio_input = gr.Audio(label="Reference Audio", type="filepath") + with gr.Row(): + gen_text_input = gr.Textbox( + label="Text to Generate", + lines=10, + max_lines=40, + scale=4, + ) + gen_text_file = gr.File(label="Load Text to Generate from File (.txt)", file_types=[".txt"], scale=1) + generate_btn = gr.Button("Synthesize", variant="primary") + with gr.Accordion("Advanced Settings", open=False): + with gr.Row(): + ref_text_input = gr.Textbox( + label="Reference Text", + info="Leave blank to automatically transcribe the reference audio. If you enter text or upload a file, it will override automatic transcription.", + lines=2, + scale=4, + ) + ref_text_file = gr.File(label="Load Reference Text from File (.txt)", file_types=[".txt"], scale=1) + with gr.Row(): + randomize_seed = gr.Checkbox( + label="Randomize Seed", + info="Check to use a random seed for each generation. Uncheck to use the seed specified.", + value=True, + scale=3, + ) + seed_input = gr.Number(show_label=False, value=0, precision=0, scale=1) + with gr.Column(scale=4): + remove_silence = gr.Checkbox( + label="Remove Silences", + info="If undesired long silence(s) produced, turn on to automatically detect and crop.", + value=False, + ) + speed_slider = gr.Slider( + label="Speed", + minimum=0.3, + maximum=2.0, + value=1.0, + step=0.1, + info="Adjust the speed of the audio.", + ) + nfe_slider = gr.Slider( + label="NFE Steps", + minimum=4, + maximum=64, + value=32, + step=2, + info="Set the number of denoising steps.", + ) + cross_fade_duration_slider = gr.Slider( + label="Cross-Fade Duration (s)", + minimum=0.0, + maximum=1.0, + value=0.15, + step=0.01, + info="Set the duration of the cross-fade between audio clips.", + ) + + audio_output = gr.Audio(label="Synthesized Audio") + spectrogram_output = gr.Image(label="Spectrogram") + + @gpu_decorator + def basic_tts( + ref_audio_input, + ref_text_input, + gen_text_input, + remove_silence, + randomize_seed, + seed_input, + cross_fade_duration_slider, + nfe_slider, + speed_slider, + ): + if randomize_seed: + seed_input = np.random.randint(0, 2**31 - 1) + + audio_out, spectrogram_path, ref_text_out, used_seed = infer( + ref_audio_input, + ref_text_input, + gen_text_input, + tts_model_choice, + remove_silence, + seed=seed_input, + cross_fade_duration=cross_fade_duration_slider, + nfe_step=nfe_slider, + speed=speed_slider, + ) + return audio_out, spectrogram_path, ref_text_out, used_seed + + gen_text_file.upload( + load_text_from_file, + inputs=[gen_text_file], + outputs=[gen_text_input], + ) + + ref_text_file.upload( + load_text_from_file, + inputs=[ref_text_file], + outputs=[ref_text_input], + ) + + ref_audio_input.clear( + lambda: [None, None], + None, + [ref_text_input, ref_text_file], + ) + + generate_btn.click( + basic_tts, + inputs=[ + ref_audio_input, + ref_text_input, + gen_text_input, + remove_silence, + randomize_seed, + seed_input, + cross_fade_duration_slider, + nfe_slider, + speed_slider, + ], + outputs=[audio_output, spectrogram_output, ref_text_input, seed_input], + ) + + +def parse_speechtypes_text(gen_text): + # Pattern to find {str} or {"name": str, "seed": int, "speed": float} + pattern = r"(\{.*?\})" + + # Split the text by the pattern + tokens = re.split(pattern, gen_text) + + segments = [] + + current_type_dict = { + "name": "Regular", + "seed": -1, + "speed": 1.0, + } + + for i in range(len(tokens)): + if i % 2 == 0: + # This is text + text = tokens[i].strip() + if text: + current_type_dict["text"] = text + segments.append(current_type_dict) + else: + # This is type + type_str = tokens[i].strip() + try: # if type dict + current_type_dict = json.loads(type_str) + except json.decoder.JSONDecodeError: + type_str = type_str[1:-1] # remove brace {} + current_type_dict = {"name": type_str, "seed": -1, "speed": 1.0} + + return segments + + +with gr.Blocks() as app_multistyle: + # New section for multistyle generation + gr.Markdown( + """ + # Multiple Speech-Type Generation + + This section allows you to generate multiple speech types or multiple people's voices. Enter your text in the format shown below, or upload a .txt file with the same format. The system will generate speech using the appropriate type. If unspecified, the model will use the regular speech type. The current speech type will be used until the next speech type is specified. + """ + ) + + with gr.Row(): + gr.Markdown( + """ + **Example Input:**
+ {Regular} Hello, I'd like to order a sandwich please.
+ {Surprised} What do you mean you're out of bread?
+ {Sad} I really wanted a sandwich though...
+ {Angry} You know what, darn you and your little shop!
+ {Whisper} I'll just go back home and cry now.
+ {Shouting} Why me?! + """ + ) + + gr.Markdown( + """ + **Example Input 2:**
+ {"name": "Speaker1_Happy", "seed": -1, "speed": 1} Hello, I'd like to order a sandwich please.
+ {"name": "Speaker2_Regular", "seed": -1, "speed": 1} Sorry, we're out of bread.
+ {"name": "Speaker1_Sad", "seed": -1, "speed": 1} I really wanted a sandwich though...
+ {"name": "Speaker2_Whisper", "seed": -1, "speed": 1} I'll give you the last one I was hiding. + """ + ) + + gr.Markdown( + 'Upload different audio clips for each speech type. The first speech type is mandatory. You can add additional speech types by clicking the "Add Speech Type" button.' + ) + + # Regular speech type (mandatory) + with gr.Row(variant="compact") as regular_row: + with gr.Column(scale=1, min_width=160): + regular_name = gr.Textbox(value="Regular", label="Speech Type Name") + regular_insert = gr.Button("Insert Label", variant="secondary") + with gr.Column(scale=3): + regular_audio = gr.Audio(label="Regular Reference Audio", type="filepath") + with gr.Column(scale=3): + regular_ref_text = gr.Textbox(label="Reference Text (Regular)", lines=4) + with gr.Row(): + regular_seed_slider = gr.Slider( + show_label=False, minimum=-1, maximum=999, value=-1, step=1, info="Seed, -1 for random" + ) + regular_speed_slider = gr.Slider( + show_label=False, minimum=0.3, maximum=2.0, value=1.0, step=0.1, info="Adjust the speed" + ) + with gr.Column(scale=1, min_width=160): + regular_ref_text_file = gr.File(label="Load Reference Text from File (.txt)", file_types=[".txt"]) + + # Regular speech type (max 100) + max_speech_types = 100 + speech_type_rows = [regular_row] + speech_type_names = [regular_name] + speech_type_audios = [regular_audio] + speech_type_ref_texts = [regular_ref_text] + speech_type_ref_text_files = [regular_ref_text_file] + speech_type_seeds = [regular_seed_slider] + speech_type_speeds = [regular_speed_slider] + speech_type_delete_btns = [None] + speech_type_insert_btns = [regular_insert] + + # Additional speech types (99 more) + for i in range(max_speech_types - 1): + with gr.Row(variant="compact", visible=False) as row: + with gr.Column(scale=1, min_width=160): + name_input = gr.Textbox(label="Speech Type Name") + insert_btn = gr.Button("Insert Label", variant="secondary") + delete_btn = gr.Button("Delete Type", variant="stop") + with gr.Column(scale=3): + audio_input = gr.Audio(label="Reference Audio", type="filepath") + with gr.Column(scale=3): + ref_text_input = gr.Textbox(label="Reference Text", lines=4) + with gr.Row(): + seed_input = gr.Slider( + show_label=False, minimum=-1, maximum=999, value=-1, step=1, info="Seed. -1 for random" + ) + speed_input = gr.Slider( + show_label=False, minimum=0.3, maximum=2.0, value=1.0, step=0.1, info="Adjust the speed" + ) + with gr.Column(scale=1, min_width=160): + ref_text_file_input = gr.File(label="Load Reference Text from File (.txt)", file_types=[".txt"]) + speech_type_rows.append(row) + speech_type_names.append(name_input) + speech_type_audios.append(audio_input) + speech_type_ref_texts.append(ref_text_input) + speech_type_ref_text_files.append(ref_text_file_input) + speech_type_seeds.append(seed_input) + speech_type_speeds.append(speed_input) + speech_type_delete_btns.append(delete_btn) + speech_type_insert_btns.append(insert_btn) + + # Global logic for all speech types + for i in range(max_speech_types): + speech_type_audios[i].clear( + lambda: [None, None], + None, + [speech_type_ref_texts[i], speech_type_ref_text_files[i]], + ) + speech_type_ref_text_files[i].upload( + load_text_from_file, + inputs=[speech_type_ref_text_files[i]], + outputs=[speech_type_ref_texts[i]], + ) + + # Button to add speech type + add_speech_type_btn = gr.Button("Add Speech Type") + + # Keep track of autoincrement of speech types, no roll back + speech_type_count = 1 + + # Function to add a speech type + def add_speech_type_fn(): + row_updates = [gr.update() for _ in range(max_speech_types)] + global speech_type_count + if speech_type_count < max_speech_types: + row_updates[speech_type_count] = gr.update(visible=True) + speech_type_count += 1 + else: + gr.Warning("Exhausted maximum number of speech types. Consider restart the app.") + return row_updates + + add_speech_type_btn.click(add_speech_type_fn, outputs=speech_type_rows) + + # Function to delete a speech type + def delete_speech_type_fn(): + return gr.update(visible=False), None, None, None, None + + # Update delete button clicks and ref text file changes + for i in range(1, len(speech_type_delete_btns)): + speech_type_delete_btns[i].click( + delete_speech_type_fn, + outputs=[ + speech_type_rows[i], + speech_type_names[i], + speech_type_audios[i], + speech_type_ref_texts[i], + speech_type_ref_text_files[i], + ], + ) + + # Text input for the prompt + with gr.Row(): + gen_text_input_multistyle = gr.Textbox( + label="Text to Generate", + lines=10, + max_lines=40, + scale=4, + placeholder="Enter the script with speaker names (or emotion types) at the start of each block, e.g.:\n\n{Regular} Hello, I'd like to order a sandwich please.\n{Surprised} What do you mean you're out of bread?\n{Sad} I really wanted a sandwich though...\n{Angry} You know what, darn you and your little shop!\n{Whisper} I'll just go back home and cry now.\n{Shouting} Why me?!", + ) + gen_text_file_multistyle = gr.File(label="Load Text to Generate from File (.txt)", file_types=[".txt"], scale=1) + + def make_insert_speech_type_fn(index): + def insert_speech_type_fn(current_text, speech_type_name, speech_type_seed, speech_type_speed): + current_text = current_text or "" + if not speech_type_name: + gr.Warning("Please enter speech type name before insert.") + return current_text + speech_type_dict = { + "name": speech_type_name, + "seed": speech_type_seed, + "speed": speech_type_speed, + } + updated_text = current_text + json.dumps(speech_type_dict) + " " + return updated_text + + return insert_speech_type_fn + + for i, insert_btn in enumerate(speech_type_insert_btns): + insert_fn = make_insert_speech_type_fn(i) + insert_btn.click( + insert_fn, + inputs=[gen_text_input_multistyle, speech_type_names[i], speech_type_seeds[i], speech_type_speeds[i]], + outputs=gen_text_input_multistyle, + ) + + with gr.Accordion("Advanced Settings", open=True): + with gr.Row(): + with gr.Column(): + show_cherrypick_multistyle = gr.Checkbox( + label="Show Cherry-pick Interface", + info="Turn on to show interface, picking seeds from previous generations.", + value=False, + ) + with gr.Column(): + remove_silence_multistyle = gr.Checkbox( + label="Remove Silences", + info="Turn on to automatically detect and crop long silences.", + value=True, + ) + + # Generate button + generate_multistyle_btn = gr.Button("Generate Multi-Style Speech", variant="primary") + + # Output audio + audio_output_multistyle = gr.Audio(label="Synthesized Audio") + + # Used seed gallery + cherrypick_interface_multistyle = gr.Textbox( + label="Cherry-pick Interface", + lines=10, + max_lines=40, + show_copy_button=True, + interactive=False, + visible=False, + ) + + # Logic control to show/hide the cherrypick interface + show_cherrypick_multistyle.change( + lambda is_visible: gr.update(visible=is_visible), + show_cherrypick_multistyle, + cherrypick_interface_multistyle, + ) + + # Function to load text to generate from file + gen_text_file_multistyle.upload( + load_text_from_file, + inputs=[gen_text_file_multistyle], + outputs=[gen_text_input_multistyle], + ) + + @gpu_decorator + def generate_multistyle_speech( + gen_text, + *args, + ): + speech_type_names_list = args[:max_speech_types] + speech_type_audios_list = args[max_speech_types : 2 * max_speech_types] + speech_type_ref_texts_list = args[2 * max_speech_types : 3 * max_speech_types] + remove_silence = args[3 * max_speech_types] + # Collect the speech types and their audios into a dict + speech_types = OrderedDict() + + ref_text_idx = 0 + for name_input, audio_input, ref_text_input in zip( + speech_type_names_list, speech_type_audios_list, speech_type_ref_texts_list + ): + if name_input and audio_input: + speech_types[name_input] = {"audio": audio_input, "ref_text": ref_text_input} + else: + speech_types[f"@{ref_text_idx}@"] = {"audio": "", "ref_text": ""} + ref_text_idx += 1 + + # Parse the gen_text into segments + segments = parse_speechtypes_text(gen_text) + + # For each segment, generate speech + generated_audio_segments = [] + current_type_name = "Regular" + inference_meta_data = "" + + for segment in segments: + name = segment["name"] + seed_input = segment["seed"] + speed = segment["speed"] + text = segment["text"] + + if name in speech_types: + current_type_name = name + else: + gr.Warning(f"Type {name} is not available, will use Regular as default.") + current_type_name = "Regular" + + try: + ref_audio = speech_types[current_type_name]["audio"] + except KeyError: + gr.Warning(f"Please provide reference audio for type {current_type_name}.") + return [None] + [speech_types[name]["ref_text"] for name in speech_types] + [None] + ref_text = speech_types[current_type_name].get("ref_text", "") + + if seed_input == -1: + seed_input = np.random.randint(0, 2**31 - 1) + + # Generate or retrieve speech for this segment + audio_out, _, ref_text_out, used_seed = infer( + ref_audio, + ref_text, + text, + tts_model_choice, + remove_silence, + seed=seed_input, + cross_fade_duration=0, + speed=speed, + show_info=print, # no pull to top when generating + ) + sr, audio_data = audio_out + + generated_audio_segments.append(audio_data) + speech_types[current_type_name]["ref_text"] = ref_text_out + inference_meta_data += json.dumps(dict(name=name, seed=used_seed, speed=speed)) + f" {text}\n" + + # Concatenate all audio segments + if generated_audio_segments: + final_audio_data = np.concatenate(generated_audio_segments) + return ( + [(sr, final_audio_data)] + + [speech_types[name]["ref_text"] for name in speech_types] + + [inference_meta_data] + ) + else: + gr.Warning("No audio generated.") + return [None] + [speech_types[name]["ref_text"] for name in speech_types] + [None] + + generate_multistyle_btn.click( + generate_multistyle_speech, + inputs=[ + gen_text_input_multistyle, + ] + + speech_type_names + + speech_type_audios + + speech_type_ref_texts + + [ + remove_silence_multistyle, + ], + outputs=[audio_output_multistyle] + speech_type_ref_texts + [cherrypick_interface_multistyle], + ) + + # Validation function to disable Generate button if speech types are missing + def validate_speech_types(gen_text, regular_name, *args): + speech_type_names_list = args + + # Collect the speech types names + speech_types_available = set() + if regular_name: + speech_types_available.add(regular_name) + for name_input in speech_type_names_list: + if name_input: + speech_types_available.add(name_input) + + # Parse the gen_text to get the speech types used + segments = parse_speechtypes_text(gen_text) + speech_types_in_text = set(segment["name"] for segment in segments) + + # Check if all speech types in text are available + missing_speech_types = speech_types_in_text - speech_types_available + + if missing_speech_types: + # Disable the generate button + return gr.update(interactive=False) + else: + # Enable the generate button + return gr.update(interactive=True) + + gen_text_input_multistyle.change( + validate_speech_types, + inputs=[gen_text_input_multistyle, regular_name] + speech_type_names, + outputs=generate_multistyle_btn, + ) + + +with gr.Blocks() as app_chat: + gr.Markdown( + """ +# Voice Chat +Have a conversation with an AI using your reference voice! +1. Upload a reference audio clip and optionally its transcript (via text or .txt file). +2. Load the chat model. +3. Record your message through your microphone or type it. +4. The AI will respond using the reference voice. +""" + ) + + chat_model_name_list = [ + "Qwen/Qwen2.5-3B-Instruct", + "microsoft/Phi-4-mini-instruct", + ] + + @gpu_decorator + def load_chat_model(chat_model_name): + show_info = gr.Info + global chat_model_state, chat_tokenizer_state + if chat_model_state is not None: + chat_model_state = None + chat_tokenizer_state = None + gc.collect() + torch.cuda.empty_cache() + + show_info(f"Loading chat model: {chat_model_name}") + chat_model_state = AutoModelForCausalLM.from_pretrained(chat_model_name, torch_dtype="auto", device_map="auto") + chat_tokenizer_state = AutoTokenizer.from_pretrained(chat_model_name) + show_info(f"Chat model {chat_model_name} loaded successfully!") + + return gr.update(visible=False), gr.update(visible=True) + + if USING_SPACES: + load_chat_model(chat_model_name_list[0]) + + chat_model_name_input = gr.Dropdown( + choices=chat_model_name_list, + value=chat_model_name_list[0], + label="Chat Model Name", + info="Enter the name of a HuggingFace chat model", + allow_custom_value=not USING_SPACES, + ) + load_chat_model_btn = gr.Button("Load Chat Model", variant="primary", visible=not USING_SPACES) + chat_interface_container = gr.Column(visible=USING_SPACES) + + chat_model_name_input.change( + lambda: gr.update(visible=True), + None, + load_chat_model_btn, + show_progress="hidden", + ) + load_chat_model_btn.click( + load_chat_model, inputs=[chat_model_name_input], outputs=[load_chat_model_btn, chat_interface_container] + ) + + with chat_interface_container: + with gr.Row(): + with gr.Column(): + ref_audio_chat = gr.Audio(label="Reference Audio", type="filepath") + with gr.Column(): + with gr.Accordion("Advanced Settings", open=False): + with gr.Row(): + ref_text_chat = gr.Textbox( + label="Reference Text", + info="Optional: Leave blank to auto-transcribe", + lines=2, + scale=3, + ) + ref_text_file_chat = gr.File( + label="Load Reference Text from File (.txt)", file_types=[".txt"], scale=1 + ) + with gr.Row(): + randomize_seed_chat = gr.Checkbox( + label="Randomize Seed", + value=True, + info="Uncheck to use the seed specified.", + scale=3, + ) + seed_input_chat = gr.Number(show_label=False, value=0, precision=0, scale=1) + remove_silence_chat = gr.Checkbox( + label="Remove Silences", + value=True, + ) + system_prompt_chat = gr.Textbox( + label="System Prompt", + value="You are not an AI assistant, you are whoever the user says you are. You must stay in character. Keep your responses concise since they will be spoken out loud.", + lines=2, + ) + + chatbot_interface = gr.Chatbot(label="Conversation", type="messages") + + with gr.Row(): + with gr.Column(): + audio_input_chat = gr.Microphone( + label="Speak your message", + type="filepath", + ) + audio_output_chat = gr.Audio(autoplay=True) + with gr.Column(): + text_input_chat = gr.Textbox( + label="Type your message", + lines=1, + ) + send_btn_chat = gr.Button("Send Message") + clear_btn_chat = gr.Button("Clear Conversation") + + # Modify process_audio_input to generate user input + @gpu_decorator + def process_audio_input(conv_state, audio_path, text): + """Handle audio or text input from user""" + + if not audio_path and not text.strip(): + return conv_state + + if audio_path: + text = preprocess_ref_audio_text(audio_path, text)[1] + if not text.strip(): + return conv_state + + conv_state.append({"role": "user", "content": text}) + return conv_state + + # Use model and tokenizer from state to get text response + @gpu_decorator + def generate_text_response(conv_state, system_prompt): + """Generate text response from AI""" + + system_prompt_state = [{"role": "system", "content": system_prompt}] + response = chat_model_inference(system_prompt_state + conv_state, chat_model_state, chat_tokenizer_state) + + conv_state.append({"role": "assistant", "content": response}) + return conv_state + + @gpu_decorator + def generate_audio_response(conv_state, ref_audio, ref_text, remove_silence, randomize_seed, seed_input): + """Generate TTS audio for AI response""" + if not conv_state or not ref_audio: + return None, ref_text, seed_input + + last_ai_response = conv_state[-1]["content"] + if not last_ai_response or conv_state[-1]["role"] != "assistant": + return None, ref_text, seed_input + + if randomize_seed: + seed_input = np.random.randint(0, 2**31 - 1) + + audio_result, _, ref_text_out, used_seed = infer( + ref_audio, + ref_text, + last_ai_response, + tts_model_choice, + remove_silence, + seed=seed_input, + cross_fade_duration=0.15, + speed=1.0, + show_info=print, # show_info=print no pull to top when generating + ) + return audio_result, ref_text_out, used_seed + + def clear_conversation(): + """Reset the conversation""" + return [], None + + ref_text_file_chat.upload( + load_text_from_file, + inputs=[ref_text_file_chat], + outputs=[ref_text_chat], + ) + + for user_operation in [audio_input_chat.stop_recording, text_input_chat.submit, send_btn_chat.click]: + user_operation( + process_audio_input, + inputs=[chatbot_interface, audio_input_chat, text_input_chat], + outputs=[chatbot_interface], + ).then( + generate_text_response, + inputs=[chatbot_interface, system_prompt_chat], + outputs=[chatbot_interface], + ).then( + generate_audio_response, + inputs=[ + chatbot_interface, + ref_audio_chat, + ref_text_chat, + remove_silence_chat, + randomize_seed_chat, + seed_input_chat, + ], + outputs=[audio_output_chat, ref_text_chat, seed_input_chat], + ).then( + lambda: [None, None], + None, + [audio_input_chat, text_input_chat], + ) + + # Handle clear button or system prompt change and reset conversation + for user_operation in [clear_btn_chat.click, system_prompt_chat.change, chatbot_interface.clear]: + user_operation( + clear_conversation, + outputs=[chatbot_interface, audio_output_chat], + ) + + +with gr.Blocks() as app_credits: + gr.Markdown(""" +# Credits + +* [mrfakename](https://github.com/fakerybakery) for the original [online demo](https://huggingface.co/spaces/mrfakename/E2-F5-TTS) +* [RootingInLoad](https://github.com/RootingInLoad) for initial chunk generation and podcast app exploration +* [jpgallegoar](https://github.com/jpgallegoar) for multiple speech-type generation & voice chat +""") + + +with gr.Blocks() as app: + gr.Markdown( + f""" +# E2/F5 TTS + +This is {"a local web UI for [F5 TTS](https://github.com/SWivid/F5-TTS)" if not USING_SPACES else "an online demo for [F5-TTS](https://github.com/SWivid/F5-TTS)"} with advanced batch processing support. This app supports the following TTS models: + +* [F5-TTS](https://arxiv.org/abs/2410.06885) (A Fairytaler that Fakes Fluent and Faithful Speech with Flow Matching) +* [E2 TTS](https://arxiv.org/abs/2406.18009) (Embarrassingly Easy Fully Non-Autoregressive Zero-Shot TTS) + +The checkpoints currently support English and Chinese. + +If you're having issues, try converting your reference audio to WAV or MP3, clipping it to 12s with ✂ in the bottom right corner (otherwise might have non-optimal auto-trimmed result). + +**NOTE: Reference text will be automatically transcribed with Whisper if not provided. For best results, keep your reference clips short (<12s). Ensure the audio is fully uploaded before generating.** +""" + ) + + last_used_custom = files("f5_tts").joinpath("infer/.cache/last_used_custom_model_info_v1.txt") + + def load_last_used_custom(): + try: + custom = [] + with open(last_used_custom, "r", encoding="utf-8") as f: + for line in f: + custom.append(line.strip()) + return custom + except FileNotFoundError: + last_used_custom.parent.mkdir(parents=True, exist_ok=True) + return DEFAULT_TTS_MODEL_CFG + + def switch_tts_model(new_choice): + global tts_model_choice + if new_choice == "Custom": # override in case webpage is refreshed + custom_ckpt_path, custom_vocab_path, custom_model_cfg = load_last_used_custom() + tts_model_choice = ("Custom", custom_ckpt_path, custom_vocab_path, custom_model_cfg) + return ( + gr.update(visible=True, value=custom_ckpt_path), + gr.update(visible=True, value=custom_vocab_path), + gr.update(visible=True, value=custom_model_cfg), + ) + else: + tts_model_choice = new_choice + return gr.update(visible=False), gr.update(visible=False), gr.update(visible=False) + + def set_custom_model(custom_ckpt_path, custom_vocab_path, custom_model_cfg): + global tts_model_choice + tts_model_choice = ("Custom", custom_ckpt_path, custom_vocab_path, custom_model_cfg) + with open(last_used_custom, "w", encoding="utf-8") as f: + f.write(custom_ckpt_path + "\n" + custom_vocab_path + "\n" + custom_model_cfg + "\n") + + with gr.Row(): + if not USING_SPACES: + choose_tts_model = gr.Radio( + choices=[DEFAULT_TTS_MODEL, "E2-TTS", "Custom"], label="Choose TTS Model", value=DEFAULT_TTS_MODEL + ) + else: + choose_tts_model = gr.Radio( + choices=[DEFAULT_TTS_MODEL, "E2-TTS"], label="Choose TTS Model", value=DEFAULT_TTS_MODEL + ) + custom_ckpt_path = gr.Dropdown( + choices=[DEFAULT_TTS_MODEL_CFG[0]], + value=load_last_used_custom()[0], + allow_custom_value=True, + label="Model: local_path | hf://user_id/repo_id/model_ckpt", + visible=False, + ) + custom_vocab_path = gr.Dropdown( + choices=[DEFAULT_TTS_MODEL_CFG[1]], + value=load_last_used_custom()[1], + allow_custom_value=True, + label="Vocab: local_path | hf://user_id/repo_id/vocab_file", + visible=False, + ) + custom_model_cfg = gr.Dropdown( + choices=[ + DEFAULT_TTS_MODEL_CFG[2], + json.dumps( + dict( + dim=1024, + depth=22, + heads=16, + ff_mult=2, + text_dim=512, + text_mask_padding=False, + conv_layers=4, + pe_attn_head=1, + ) + ), + json.dumps( + dict( + dim=768, + depth=18, + heads=12, + ff_mult=2, + text_dim=512, + text_mask_padding=False, + conv_layers=4, + pe_attn_head=1, + ) + ), + ], + value=load_last_used_custom()[2], + allow_custom_value=True, + label="Config: in a dictionary form", + visible=False, + ) + + choose_tts_model.change( + switch_tts_model, + inputs=[choose_tts_model], + outputs=[custom_ckpt_path, custom_vocab_path, custom_model_cfg], + show_progress="hidden", + ) + custom_ckpt_path.change( + set_custom_model, + inputs=[custom_ckpt_path, custom_vocab_path, custom_model_cfg], + show_progress="hidden", + ) + custom_vocab_path.change( + set_custom_model, + inputs=[custom_ckpt_path, custom_vocab_path, custom_model_cfg], + show_progress="hidden", + ) + custom_model_cfg.change( + set_custom_model, + inputs=[custom_ckpt_path, custom_vocab_path, custom_model_cfg], + show_progress="hidden", + ) + + gr.TabbedInterface( + [app_tts, app_multistyle, app_chat, app_credits], + ["Basic-TTS", "Multi-Speech", "Voice-Chat", "Credits"], + ) + + +@click.command() +@click.option("--port", "-p", default=None, type=int, help="Port to run the app on") +@click.option("--host", "-H", default=None, help="Host to run the app on") +@click.option( + "--share", + "-s", + default=False, + is_flag=True, + help="Share the app via Gradio share link", +) +@click.option("--api", "-a", default=True, is_flag=True, help="Allow API access") +@click.option( + "--root_path", + "-r", + default=None, + type=str, + help='The root path (or "mount point") of the application, if it\'s not served from the root ("/") of the domain. Often used when the application is behind a reverse proxy that forwards requests to the application, e.g. set "/myapp" or full URL for application served at "https://example.com/myapp".', +) +@click.option( + "--inbrowser", + "-i", + is_flag=True, + default=False, + help="Automatically launch the interface in the default web browser", +) +def main(port, host, share, api, root_path, inbrowser): + global app + print("Starting app...") + app.queue(api_open=api).launch( + server_name=host, + server_port=port, + share=share, + show_api=api, + root_path=root_path, + inbrowser=inbrowser, + ) + + +if __name__ == "__main__": + if not USING_SPACES: + main() + else: + app.queue().launch() diff --git a/src/f5_tts/src/f5_tts/infer/speech_edit.py b/src/f5_tts/src/f5_tts/infer/speech_edit.py new file mode 100644 index 0000000000000000000000000000000000000000..367832d8c2be819abfb04f4c24d8c4811e402e68 --- /dev/null +++ b/src/f5_tts/src/f5_tts/infer/speech_edit.py @@ -0,0 +1,205 @@ +import os + + +os.environ["PYTORCH_ENABLE_MPS_FALLBACK"] = "1" # for MPS device compatibility + +from importlib.resources import files + +import torch +import torch.nn.functional as F +import torchaudio +from cached_path import cached_path +from hydra.utils import get_class +from omegaconf import OmegaConf + +from f5_tts.infer.utils_infer import load_checkpoint, load_vocoder, save_spectrogram +from f5_tts.model import CFM +from f5_tts.model.utils import convert_char_to_pinyin, get_tokenizer + + +device = ( + "cuda" + if torch.cuda.is_available() + else "xpu" + if torch.xpu.is_available() + else "mps" + if torch.backends.mps.is_available() + else "cpu" +) + + +# ---------------------- infer setting ---------------------- # + +seed = None # int | None + +exp_name = "F5TTS_v1_Base" # F5TTS_v1_Base | E2TTS_Base +ckpt_step = 1250000 + +nfe_step = 32 # 16, 32 +cfg_strength = 2.0 +ode_method = "euler" # euler | midpoint +sway_sampling_coef = -1.0 +speed = 1.0 +target_rms = 0.1 + + +model_cfg = OmegaConf.load(str(files("f5_tts").joinpath(f"configs/{exp_name}.yaml"))) +model_cls = get_class(f"f5_tts.model.{model_cfg.model.backbone}") +model_arc = model_cfg.model.arch + +dataset_name = model_cfg.datasets.name +tokenizer = model_cfg.model.tokenizer + +mel_spec_type = model_cfg.model.mel_spec.mel_spec_type +target_sample_rate = model_cfg.model.mel_spec.target_sample_rate +n_mel_channels = model_cfg.model.mel_spec.n_mel_channels +hop_length = model_cfg.model.mel_spec.hop_length +win_length = model_cfg.model.mel_spec.win_length +n_fft = model_cfg.model.mel_spec.n_fft + + +# ckpt_path = str(files("f5_tts").joinpath("../../")) + f"/ckpts/{exp_name}/model_{ckpt_step}.safetensors" +ckpt_path = str(cached_path(f"hf://SWivid/F5-TTS/{exp_name}/model_{ckpt_step}.safetensors")) +output_dir = "tests" + + +# [leverage https://github.com/MahmoudAshraf97/ctc-forced-aligner to get char level alignment] +# pip install git+https://github.com/MahmoudAshraf97/ctc-forced-aligner.git +# [write the origin_text into a file, e.g. tests/test_edit.txt] +# ctc-forced-aligner --audio_path "src/f5_tts/infer/examples/basic/basic_ref_en.wav" --text_path "tests/test_edit.txt" --language "zho" --romanize --split_size "char" +# [result will be saved at same path of audio file] +# [--language "zho" for Chinese, "eng" for English] +# [if local ckpt, set --alignment_model "../checkpoints/mms-300m-1130-forced-aligner"] + +audio_to_edit = str(files("f5_tts").joinpath("infer/examples/basic/basic_ref_en.wav")) +origin_text = "Some call me nature, others call me mother nature." +target_text = "Some call me optimist, others call me realist." +parts_to_edit = [ + [1.42, 2.44], + [4.04, 4.9], +] # stard_ends of "nature" & "mother nature", in seconds +fix_duration = [ + 1.2, + 1, +] # fix duration for "optimist" & "realist", in seconds + +# audio_to_edit = "src/f5_tts/infer/examples/basic/basic_ref_zh.wav" +# origin_text = "对,这就是我,万人敬仰的太乙真人。" +# target_text = "对,那就是你,万人敬仰的太白金星。" +# parts_to_edit = [[0.84, 1.4], [1.92, 2.4], [4.26, 6.26], ] +# fix_duration = None # use origin text duration + + +# -------------------------------------------------# + +use_ema = True + +if not os.path.exists(output_dir): + os.makedirs(output_dir) + +# Vocoder model +local = False +if mel_spec_type == "vocos": + vocoder_local_path = "../checkpoints/charactr/vocos-mel-24khz" +elif mel_spec_type == "bigvgan": + vocoder_local_path = "../checkpoints/bigvgan_v2_24khz_100band_256x" +vocoder = load_vocoder(vocoder_name=mel_spec_type, is_local=local, local_path=vocoder_local_path) + +# Tokenizer +vocab_char_map, vocab_size = get_tokenizer(dataset_name, tokenizer) + +# Model +model = CFM( + transformer=model_cls(**model_arc, text_num_embeds=vocab_size, mel_dim=n_mel_channels), + mel_spec_kwargs=dict( + n_fft=n_fft, + hop_length=hop_length, + win_length=win_length, + n_mel_channels=n_mel_channels, + target_sample_rate=target_sample_rate, + mel_spec_type=mel_spec_type, + ), + odeint_kwargs=dict( + method=ode_method, + ), + vocab_char_map=vocab_char_map, +).to(device) + +dtype = torch.float32 if mel_spec_type == "bigvgan" else None +model = load_checkpoint(model, ckpt_path, device, dtype=dtype, use_ema=use_ema) + +# Audio +audio, sr = torchaudio.load(audio_to_edit) +if audio.shape[0] > 1: + audio = torch.mean(audio, dim=0, keepdim=True) +rms = torch.sqrt(torch.mean(torch.square(audio))) +if rms < target_rms: + audio = audio * target_rms / rms +if sr != target_sample_rate: + resampler = torchaudio.transforms.Resample(sr, target_sample_rate) + audio = resampler(audio) +offset = 0 +audio_ = torch.zeros(1, 0) +edit_mask = torch.zeros(1, 0, dtype=torch.bool) +for part in parts_to_edit: + start, end = part + part_dur = end - start if fix_duration is None else fix_duration.pop(0) + part_dur = part_dur * target_sample_rate + start = start * target_sample_rate + audio_ = torch.cat((audio_, audio[:, round(offset) : round(start)], torch.zeros(1, round(part_dur))), dim=-1) + edit_mask = torch.cat( + ( + edit_mask, + torch.ones(1, round((start - offset) / hop_length), dtype=torch.bool), + torch.zeros(1, round(part_dur / hop_length), dtype=torch.bool), + ), + dim=-1, + ) + offset = end * target_sample_rate +audio = torch.cat((audio_, audio[:, round(offset) :]), dim=-1) +edit_mask = F.pad(edit_mask, (0, audio.shape[-1] // hop_length - edit_mask.shape[-1] + 1), value=True) +audio = audio.to(device) +edit_mask = edit_mask.to(device) + +# Text +text_list = [target_text] +if tokenizer == "pinyin": + final_text_list = convert_char_to_pinyin(text_list) +else: + final_text_list = [text_list] +print(f"text : {text_list}") +print(f"pinyin: {final_text_list}") + +# Duration +ref_audio_len = 0 +duration = audio.shape[-1] // hop_length + +# Inference +with torch.inference_mode(): + generated, trajectory = model.sample( + cond=audio, + text=final_text_list, + duration=duration, + steps=nfe_step, + cfg_strength=cfg_strength, + sway_sampling_coef=sway_sampling_coef, + seed=seed, + edit_mask=edit_mask, + ) + print(f"Generated mel: {generated.shape}") + + # Final result + generated = generated.to(torch.float32) + generated = generated[:, ref_audio_len:, :] + gen_mel_spec = generated.permute(0, 2, 1) + if mel_spec_type == "vocos": + generated_wave = vocoder.decode(gen_mel_spec).cpu() + elif mel_spec_type == "bigvgan": + generated_wave = vocoder(gen_mel_spec).squeeze(0).cpu() + + if rms < target_rms: + generated_wave = generated_wave * rms / target_rms + + save_spectrogram(gen_mel_spec[0].cpu().numpy(), f"{output_dir}/speech_edit_out.png") + torchaudio.save(f"{output_dir}/speech_edit_out.wav", generated_wave, target_sample_rate) + print(f"Generated wav: {generated_wave.shape}") diff --git a/src/f5_tts/src/f5_tts/infer/utils_infer.py b/src/f5_tts/src/f5_tts/infer/utils_infer.py new file mode 100644 index 0000000000000000000000000000000000000000..1b7437fcb328c40322cf847793b1b352741dfb08 --- /dev/null +++ b/src/f5_tts/src/f5_tts/infer/utils_infer.py @@ -0,0 +1,605 @@ +# A unified script for inference process +# Make adjustments inside functions, and consider both gradio and cli scripts if need to change func output format +import os +import sys +from concurrent.futures import ThreadPoolExecutor + + +os.environ["PYTORCH_ENABLE_MPS_FALLBACK"] = "1" # for MPS device compatibility +sys.path.append(f"{os.path.dirname(os.path.abspath(__file__))}/../../third_party/BigVGAN/") + +import hashlib +import re +import tempfile +from importlib.resources import files + +import matplotlib + + +matplotlib.use("Agg") + +import matplotlib.pylab as plt +import numpy as np +import torch +import torchaudio +import tqdm +from huggingface_hub import hf_hub_download +from pydub import AudioSegment, silence +from transformers import pipeline +from vocos import Vocos + +from f5_tts.model import CFM +from f5_tts.model.utils import convert_char_to_pinyin, get_tokenizer + + +_ref_audio_cache = {} +_ref_text_cache = {} + +device = ( + "cuda" + if torch.cuda.is_available() + else "xpu" + if torch.xpu.is_available() + else "mps" + if torch.backends.mps.is_available() + else "cpu" +) + +tempfile_kwargs = {"delete_on_close": False} if sys.version_info >= (3, 12) else {"delete": False} + +# ----------------------------------------- + +target_sample_rate = 24000 +n_mel_channels = 100 +hop_length = 256 +win_length = 1024 +n_fft = 1024 +mel_spec_type = "vocos" +target_rms = 0.1 +cross_fade_duration = 0.15 +ode_method = "euler" +nfe_step = 32 # 16, 32 +cfg_strength = 2.0 +sway_sampling_coef = -1.0 +speed = 1.0 +fix_duration = None + +# ----------------------------------------- + + +# chunk text into smaller pieces + + +def chunk_text(text, max_chars=135): + """ + Splits the input text into chunks, each with a maximum number of characters. + + Args: + text (str): The text to be split. + max_chars (int): The maximum number of characters per chunk. + + Returns: + List[str]: A list of text chunks. + """ + chunks = [] + current_chunk = "" + # Split the text into sentences based on punctuation followed by whitespace + sentences = re.split(r"(?<=[;:,.!?])\s+|(?<=[;:,。!?])", text) + + for sentence in sentences: + if len(current_chunk.encode("utf-8")) + len(sentence.encode("utf-8")) <= max_chars: + current_chunk += sentence + " " if sentence and len(sentence[-1].encode("utf-8")) == 1 else sentence + else: + if current_chunk: + chunks.append(current_chunk.strip()) + current_chunk = sentence + " " if sentence and len(sentence[-1].encode("utf-8")) == 1 else sentence + + if current_chunk: + chunks.append(current_chunk.strip()) + + return chunks + + +# load vocoder +def load_vocoder(vocoder_name="vocos", is_local=False, local_path="", device=device, hf_cache_dir=None): + if vocoder_name == "vocos": + # vocoder = Vocos.from_pretrained("charactr/vocos-mel-24khz").to(device) + if is_local: + print(f"Load vocos from local path {local_path}") + config_path = f"{local_path}/config.yaml" + model_path = f"{local_path}/pytorch_model.bin" + else: + print("Download Vocos from huggingface charactr/vocos-mel-24khz") + repo_id = "charactr/vocos-mel-24khz" + config_path = hf_hub_download(repo_id=repo_id, cache_dir=hf_cache_dir, filename="config.yaml") + model_path = hf_hub_download(repo_id=repo_id, cache_dir=hf_cache_dir, filename="pytorch_model.bin") + vocoder = Vocos.from_hparams(config_path) + state_dict = torch.load(model_path, map_location="cpu", weights_only=True) + from vocos.feature_extractors import EncodecFeatures + + if isinstance(vocoder.feature_extractor, EncodecFeatures): + encodec_parameters = { + "feature_extractor.encodec." + key: value + for key, value in vocoder.feature_extractor.encodec.state_dict().items() + } + state_dict.update(encodec_parameters) + vocoder.load_state_dict(state_dict) + vocoder = vocoder.eval().to(device) + elif vocoder_name == "bigvgan": + try: + from third_party.BigVGAN import bigvgan + except ImportError: + print("You need to follow the README to init submodule and change the BigVGAN source code.") + if is_local: + # download generator from https://huggingface.co/nvidia/bigvgan_v2_24khz_100band_256x/tree/main + vocoder = bigvgan.BigVGAN.from_pretrained(local_path, use_cuda_kernel=False) + else: + vocoder = bigvgan.BigVGAN.from_pretrained( + "nvidia/bigvgan_v2_24khz_100band_256x", use_cuda_kernel=False, cache_dir=hf_cache_dir + ) + + vocoder.remove_weight_norm() + vocoder = vocoder.eval().to(device) + return vocoder + + +# load asr pipeline + +asr_pipe = None + + +def initialize_asr_pipeline(device: str = device, dtype=None): + if dtype is None: + dtype = ( + torch.float16 + if "cuda" in device + and torch.cuda.get_device_properties(device).major >= 7 + and not torch.cuda.get_device_name().endswith("[ZLUDA]") + else torch.float32 + ) + global asr_pipe + asr_pipe = pipeline( + "automatic-speech-recognition", + model="openai/whisper-large-v3-turbo", + torch_dtype=dtype, + device=device, + ) + + +# transcribe + + +def transcribe(ref_audio, language=None): + global asr_pipe + if asr_pipe is None: + initialize_asr_pipeline(device=device) + return asr_pipe( + ref_audio, + chunk_length_s=30, + batch_size=128, + generate_kwargs={"task": "transcribe", "language": language} if language else {"task": "transcribe"}, + return_timestamps=False, + )["text"].strip() + + +# load model checkpoint for inference + + +def load_checkpoint(model, ckpt_path, device: str, dtype=None, use_ema=True): + if dtype is None: + dtype = ( + torch.float16 + if "cuda" in device + and torch.cuda.get_device_properties(device).major >= 7 + and not torch.cuda.get_device_name().endswith("[ZLUDA]") + else torch.float32 + ) + model = model.to(dtype) + + ckpt_type = ckpt_path.split(".")[-1] + if ckpt_type == "safetensors": + from safetensors.torch import load_file + + checkpoint = load_file(ckpt_path, device=device) + else: + checkpoint = torch.load(ckpt_path, map_location=device, weights_only=True) + + if use_ema: + if ckpt_type == "safetensors": + checkpoint = {"ema_model_state_dict": checkpoint} + checkpoint["model_state_dict"] = { + k.replace("ema_model.", ""): v + for k, v in checkpoint["ema_model_state_dict"].items() + if k not in ["initted", "step"] + } + + # patch for backward compatibility, 305e3ea + for key in ["mel_spec.mel_stft.mel_scale.fb", "mel_spec.mel_stft.spectrogram.window"]: + if key in checkpoint["model_state_dict"]: + del checkpoint["model_state_dict"][key] + + model.load_state_dict(checkpoint["model_state_dict"]) + else: + if ckpt_type == "safetensors": + checkpoint = {"model_state_dict": checkpoint} + model.load_state_dict(checkpoint["model_state_dict"]) + + del checkpoint + torch.cuda.empty_cache() + + return model.to(device) + + +# load model for inference + + +def load_model( + model_cls, + model_cfg, + ckpt_path, + mel_spec_type=mel_spec_type, + vocab_file="", + ode_method=ode_method, + use_ema=True, + device=device, +): + if vocab_file == "": + vocab_file = str(files("f5_tts").joinpath("infer/examples/vocab.txt")) + tokenizer = "custom" + + print("\nvocab : ", vocab_file) + print("token : ", tokenizer) + print("model : ", ckpt_path, "\n") + + vocab_char_map, vocab_size = get_tokenizer(vocab_file, tokenizer) + model = CFM( + transformer=model_cls(**model_cfg, text_num_embeds=vocab_size, mel_dim=n_mel_channels), + mel_spec_kwargs=dict( + n_fft=n_fft, + hop_length=hop_length, + win_length=win_length, + n_mel_channels=n_mel_channels, + target_sample_rate=target_sample_rate, + mel_spec_type=mel_spec_type, + ), + odeint_kwargs=dict( + method=ode_method, + ), + vocab_char_map=vocab_char_map, + ).to(device) + + dtype = torch.float32 if mel_spec_type == "bigvgan" else None + model = load_checkpoint(model, ckpt_path, device, dtype=dtype, use_ema=use_ema) + + return model + + +def remove_silence_edges(audio, silence_threshold=-42): + # Remove silence from the start + non_silent_start_idx = silence.detect_leading_silence(audio, silence_threshold=silence_threshold) + audio = audio[non_silent_start_idx:] + + # Remove silence from the end + non_silent_end_duration = audio.duration_seconds + for ms in reversed(audio): + if ms.dBFS > silence_threshold: + break + non_silent_end_duration -= 0.001 + trimmed_audio = audio[: int(non_silent_end_duration * 1000)] + + return trimmed_audio + + +# preprocess reference audio and text + + +def preprocess_ref_audio_text(ref_audio_orig, ref_text, show_info=print): + show_info("Converting audio...") + + # Compute a hash of the reference audio file + with open(ref_audio_orig, "rb") as audio_file: + audio_data = audio_file.read() + audio_hash = hashlib.md5(audio_data).hexdigest() + + global _ref_audio_cache + + if audio_hash in _ref_audio_cache: + show_info("Using cached preprocessed reference audio...") + ref_audio = _ref_audio_cache[audio_hash] + + else: # first pass, do preprocess + with tempfile.NamedTemporaryFile(suffix=".wav", **tempfile_kwargs) as f: + temp_path = f.name + + aseg = AudioSegment.from_file(ref_audio_orig) + + # 1. try to find long silence for clipping + non_silent_segs = silence.split_on_silence( + aseg, min_silence_len=1000, silence_thresh=-50, keep_silence=1000, seek_step=10 + ) + non_silent_wave = AudioSegment.silent(duration=0) + for non_silent_seg in non_silent_segs: + if len(non_silent_wave) > 6000 and len(non_silent_wave + non_silent_seg) > 12000: + show_info("Audio is over 12s, clipping short. (1)") + break + non_silent_wave += non_silent_seg + + # 2. try to find short silence for clipping if 1. failed + if len(non_silent_wave) > 12000: + non_silent_segs = silence.split_on_silence( + aseg, min_silence_len=100, silence_thresh=-40, keep_silence=1000, seek_step=10 + ) + non_silent_wave = AudioSegment.silent(duration=0) + for non_silent_seg in non_silent_segs: + if len(non_silent_wave) > 6000 and len(non_silent_wave + non_silent_seg) > 12000: + show_info("Audio is over 12s, clipping short. (2)") + break + non_silent_wave += non_silent_seg + + aseg = non_silent_wave + + # 3. if no proper silence found for clipping + if len(aseg) > 12000: + aseg = aseg[:12000] + show_info("Audio is over 12s, clipping short. (3)") + + aseg = remove_silence_edges(aseg) + AudioSegment.silent(duration=50) + aseg.export(temp_path, format="wav") + ref_audio = temp_path + + # Cache the processed reference audio + _ref_audio_cache[audio_hash] = ref_audio + + if not ref_text.strip(): + global _ref_text_cache + if audio_hash in _ref_text_cache: + # Use cached asr transcription + show_info("Using cached reference text...") + ref_text = _ref_text_cache[audio_hash] + else: + show_info("No reference text provided, transcribing reference audio...") + ref_text = transcribe(ref_audio) + # Cache the transcribed text (not caching custom ref_text, enabling users to do manual tweak) + _ref_text_cache[audio_hash] = ref_text + else: + show_info("Using custom reference text...") + + # Ensure ref_text ends with a proper sentence-ending punctuation + if not ref_text.endswith(". ") and not ref_text.endswith("。"): + if ref_text.endswith("."): + ref_text += " " + else: + ref_text += ". " + + print("\nref_text ", ref_text) + + return ref_audio, ref_text + + +# infer process: chunk text -> infer batches [i.e. infer_batch_process()] + + +def infer_process( + ref_audio, + ref_text, + gen_text, + model_obj, + vocoder, + mel_spec_type=mel_spec_type, + show_info=print, + progress=tqdm, + target_rms=target_rms, + cross_fade_duration=cross_fade_duration, + nfe_step=nfe_step, + cfg_strength=cfg_strength, + sway_sampling_coef=sway_sampling_coef, + speed=speed, + fix_duration=fix_duration, + device=device, +): + # Split the input text into batches + audio, sr = torchaudio.load(ref_audio) + max_chars = int(len(ref_text.encode("utf-8")) / (audio.shape[-1] / sr) * (22 - audio.shape[-1] / sr) * speed) + gen_text_batches = chunk_text(gen_text, max_chars=max_chars) + for i, gen_text in enumerate(gen_text_batches): + print(f"gen_text {i}", gen_text) + print("\n") + + show_info(f"Generating audio in {len(gen_text_batches)} batches...") + return next( + infer_batch_process( + (audio, sr), + ref_text, + gen_text_batches, + model_obj, + vocoder, + mel_spec_type=mel_spec_type, + progress=progress, + target_rms=target_rms, + cross_fade_duration=cross_fade_duration, + nfe_step=nfe_step, + cfg_strength=cfg_strength, + sway_sampling_coef=sway_sampling_coef, + speed=speed, + fix_duration=fix_duration, + device=device, + ) + ) + + +# infer batches + + +def infer_batch_process( + ref_audio, + ref_text, + gen_text_batches, + model_obj, + vocoder, + mel_spec_type="vocos", + progress=tqdm, + target_rms=0.1, + cross_fade_duration=0.15, + nfe_step=32, + cfg_strength=2.0, + sway_sampling_coef=-1, + speed=1, + fix_duration=None, + device=None, + streaming=False, + chunk_size=2048, +): + audio, sr = ref_audio + if audio.shape[0] > 1: + audio = torch.mean(audio, dim=0, keepdim=True) + + rms = torch.sqrt(torch.mean(torch.square(audio))) + if rms < target_rms: + audio = audio * target_rms / rms + if sr != target_sample_rate: + resampler = torchaudio.transforms.Resample(sr, target_sample_rate) + audio = resampler(audio) + audio = audio.to(device) + + generated_waves = [] + spectrograms = [] + + if len(ref_text[-1].encode("utf-8")) == 1: + ref_text = ref_text + " " + + def process_batch(gen_text): + local_speed = speed + if len(gen_text.encode("utf-8")) < 10: + local_speed = 0.3 + + # Prepare the text + text_list = [ref_text + gen_text] + final_text_list = convert_char_to_pinyin(text_list) + + ref_audio_len = audio.shape[-1] // hop_length + if fix_duration is not None: + duration = int(fix_duration * target_sample_rate / hop_length) + else: + # Calculate duration + ref_text_len = len(ref_text.encode("utf-8")) + gen_text_len = len(gen_text.encode("utf-8")) + duration = ref_audio_len + int(ref_audio_len / ref_text_len * gen_text_len / local_speed) + + # inference + with torch.inference_mode(): + generated, _ = model_obj.sample( + cond=audio, + text=final_text_list, + duration=duration, + steps=nfe_step, + cfg_strength=cfg_strength, + sway_sampling_coef=sway_sampling_coef, + ) + del _ + + generated = generated.to(torch.float32) # generated mel spectrogram + generated = generated[:, ref_audio_len:, :] + generated = generated.permute(0, 2, 1) + if mel_spec_type == "vocos": + generated_wave = vocoder.decode(generated) + elif mel_spec_type == "bigvgan": + generated_wave = vocoder(generated) + if rms < target_rms: + generated_wave = generated_wave * rms / target_rms + + # wav -> numpy + generated_wave = generated_wave.squeeze().cpu().numpy() + + if streaming: + for j in range(0, len(generated_wave), chunk_size): + yield generated_wave[j : j + chunk_size], target_sample_rate + else: + generated_cpu = generated[0].cpu().numpy() + del generated + yield generated_wave, generated_cpu + + if streaming: + for gen_text in progress.tqdm(gen_text_batches) if progress is not None else gen_text_batches: + for chunk in process_batch(gen_text): + yield chunk + else: + with ThreadPoolExecutor() as executor: + futures = [executor.submit(process_batch, gen_text) for gen_text in gen_text_batches] + for future in progress.tqdm(futures) if progress is not None else futures: + result = future.result() + if result: + generated_wave, generated_mel_spec = next(result) + generated_waves.append(generated_wave) + spectrograms.append(generated_mel_spec) + + if generated_waves: + if cross_fade_duration <= 0: + # Simply concatenate + final_wave = np.concatenate(generated_waves) + else: + # Combine all generated waves with cross-fading + final_wave = generated_waves[0] + for i in range(1, len(generated_waves)): + prev_wave = final_wave + next_wave = generated_waves[i] + + # Calculate cross-fade samples, ensuring it does not exceed wave lengths + cross_fade_samples = int(cross_fade_duration * target_sample_rate) + cross_fade_samples = min(cross_fade_samples, len(prev_wave), len(next_wave)) + + if cross_fade_samples <= 0: + # No overlap possible, concatenate + final_wave = np.concatenate([prev_wave, next_wave]) + continue + + # Overlapping parts + prev_overlap = prev_wave[-cross_fade_samples:] + next_overlap = next_wave[:cross_fade_samples] + + # Fade out and fade in + fade_out = np.linspace(1, 0, cross_fade_samples) + fade_in = np.linspace(0, 1, cross_fade_samples) + + # Cross-faded overlap + cross_faded_overlap = prev_overlap * fade_out + next_overlap * fade_in + + # Combine + new_wave = np.concatenate( + [prev_wave[:-cross_fade_samples], cross_faded_overlap, next_wave[cross_fade_samples:]] + ) + + final_wave = new_wave + + # Create a combined spectrogram + combined_spectrogram = np.concatenate(spectrograms, axis=1) + + yield final_wave, target_sample_rate, combined_spectrogram + + else: + yield None, target_sample_rate, None + + +# remove silence from generated wav + + +def remove_silence_for_generated_wav(filename): + aseg = AudioSegment.from_file(filename) + non_silent_segs = silence.split_on_silence( + aseg, min_silence_len=1000, silence_thresh=-50, keep_silence=500, seek_step=10 + ) + non_silent_wave = AudioSegment.silent(duration=0) + for non_silent_seg in non_silent_segs: + non_silent_wave += non_silent_seg + aseg = non_silent_wave + aseg.export(filename, format="wav") + + +# save spectrogram + + +def save_spectrogram(spectrogram, path): + plt.figure(figsize=(12, 4)) + plt.imshow(spectrogram, origin="lower", aspect="auto") + plt.colorbar() + plt.savefig(path) + plt.close() diff --git a/src/f5_tts/src/f5_tts/model/__init__.py b/src/f5_tts/src/f5_tts/model/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..5fed8ed7685a1d09d8074d6817cdf168d0346941 --- /dev/null +++ b/src/f5_tts/src/f5_tts/model/__init__.py @@ -0,0 +1,8 @@ +from f5_tts.model.backbones.dit import DiT +from f5_tts.model.backbones.mmdit import MMDiT +from f5_tts.model.backbones.unett import UNetT +from f5_tts.model.cfm import CFM +from f5_tts.model.trainer import Trainer + + +__all__ = ["CFM", "UNetT", "DiT", "MMDiT", "Trainer"] diff --git a/src/f5_tts/src/f5_tts/model/backbones/README.md b/src/f5_tts/src/f5_tts/model/backbones/README.md new file mode 100644 index 0000000000000000000000000000000000000000..2f3aaad7a0b5bf8ab5f816e329f5ebed45a13e2d --- /dev/null +++ b/src/f5_tts/src/f5_tts/model/backbones/README.md @@ -0,0 +1,20 @@ +## Backbones quick introduction + + +### unett.py +- flat unet transformer +- structure same as in e2-tts & voicebox paper except using rotary pos emb +- possible abs pos emb & convnextv2 blocks for embedded text before concat + +### dit.py +- adaln-zero dit +- embedded timestep as condition +- concatted noised_input + masked_cond + embedded_text, linear proj in +- possible abs pos emb & convnextv2 blocks for embedded text before concat +- possible long skip connection (first layer to last layer) + +### mmdit.py +- stable diffusion 3 block structure +- timestep as condition +- left stream: text embedded and applied a abs pos emb +- right stream: masked_cond & noised_input concatted and with same conv pos emb as unett diff --git a/src/f5_tts/src/f5_tts/model/backbones/dit.py b/src/f5_tts/src/f5_tts/model/backbones/dit.py new file mode 100644 index 0000000000000000000000000000000000000000..af63a365a9703be8cd90ec725ef7b5f73798fef1 --- /dev/null +++ b/src/f5_tts/src/f5_tts/model/backbones/dit.py @@ -0,0 +1,259 @@ +""" +ein notation: +b - batch +n - sequence +nt - text sequence +nw - raw wave length +d - dimension +""" + +from __future__ import annotations + +import torch +import torch.nn.functional as F +from torch import nn +from x_transformers.x_transformers import RotaryEmbedding + +from f5_tts.model.modules import ( + AdaLayerNorm_Final, + ConvNeXtV2Block, + ConvPositionEmbedding, + DiTBlock, + TimestepEmbedding, + get_pos_embed_indices, + precompute_freqs_cis, +) + + +# Text embedding + + +class TextEmbedding(nn.Module): + def __init__(self, text_num_embeds, text_dim, mask_padding=True, conv_layers=0, conv_mult=2): + super().__init__() + self.text_embed = nn.Embedding(text_num_embeds + 1, text_dim) # use 0 as filler token + + self.mask_padding = mask_padding # mask filler and batch padding tokens or not + + if conv_layers > 0: + self.extra_modeling = True + self.precompute_max_pos = 4096 # ~44s of 24khz audio + self.register_buffer("freqs_cis", precompute_freqs_cis(text_dim, self.precompute_max_pos), persistent=False) + self.text_blocks = nn.Sequential( + *[ConvNeXtV2Block(text_dim, text_dim * conv_mult) for _ in range(conv_layers)] + ) + else: + self.extra_modeling = False + + def forward(self, text: int["b nt"], seq_len, drop_text=False): # noqa: F722 + text = text + 1 # use 0 as filler token. preprocess of batch pad -1, see list_str_to_idx() + text = text[:, :seq_len] # curtail if character tokens are more than the mel spec tokens + batch, text_len = text.shape[0], text.shape[1] + text = F.pad(text, (0, seq_len - text_len), value=0) + if self.mask_padding: + text_mask = text == 0 + + if drop_text: # cfg for text + text = torch.zeros_like(text) + + text = self.text_embed(text) # b n -> b n d + + # possible extra modeling + if self.extra_modeling: + # sinus pos emb + batch_start = torch.zeros((batch,), dtype=torch.long) + pos_idx = get_pos_embed_indices(batch_start, seq_len, max_pos=self.precompute_max_pos) + text_pos_embed = self.freqs_cis[pos_idx] + text = text + text_pos_embed + + # convnextv2 blocks + if self.mask_padding: + text = text.masked_fill(text_mask.unsqueeze(-1).expand(-1, -1, text.size(-1)), 0.0) + for block in self.text_blocks: + text = block(text) + text = text.masked_fill(text_mask.unsqueeze(-1).expand(-1, -1, text.size(-1)), 0.0) + else: + text = self.text_blocks(text) + + return text + + +# noised input audio and context mixing embedding + + +class InputEmbedding(nn.Module): + def __init__(self, mel_dim, text_dim, out_dim): + super().__init__() + self.proj = nn.Linear(mel_dim * 2 + text_dim, out_dim) + self.conv_pos_embed = ConvPositionEmbedding(dim=out_dim) + + def forward(self, x: float["b n d"], cond: float["b n d"], text_embed: float["b n d"], drop_audio_cond=False): # noqa: F722 + if drop_audio_cond: # cfg for cond audio + cond = torch.zeros_like(cond) + + x = self.proj(torch.cat((x, cond, text_embed), dim=-1)) + x = self.conv_pos_embed(x) + x + return x + + +# Transformer backbone using DiT blocks + + +class DiT(nn.Module): + def __init__( + self, + *, + dim, + depth=8, + heads=8, + dim_head=64, + dropout=0.1, + ff_mult=4, + mel_dim=100, + text_num_embeds=256, + text_dim=None, + text_mask_padding=True, + qk_norm=None, + conv_layers=0, + pe_attn_head=None, + attn_backend="torch", # "torch" | "flash_attn" + attn_mask_enabled=False, + long_skip_connection=False, + checkpoint_activations=False, + ): + super().__init__() + + self.time_embed = TimestepEmbedding(dim) + if text_dim is None: + text_dim = mel_dim + self.text_embed = TextEmbedding( + text_num_embeds, text_dim, mask_padding=text_mask_padding, conv_layers=conv_layers + ) + self.text_cond, self.text_uncond = None, None # text cache + self.input_embed = InputEmbedding(mel_dim, text_dim, dim) + + self.rotary_embed = RotaryEmbedding(dim_head) + + self.dim = dim + self.depth = depth + + self.transformer_blocks = nn.ModuleList( + [ + DiTBlock( + dim=dim, + heads=heads, + dim_head=dim_head, + ff_mult=ff_mult, + dropout=dropout, + qk_norm=qk_norm, + pe_attn_head=pe_attn_head, + attn_backend=attn_backend, + attn_mask_enabled=attn_mask_enabled, + ) + for _ in range(depth) + ] + ) + self.long_skip_connection = nn.Linear(dim * 2, dim, bias=False) if long_skip_connection else None + + self.norm_out = AdaLayerNorm_Final(dim) # final modulation + self.proj_out = nn.Linear(dim, mel_dim) + + self.checkpoint_activations = checkpoint_activations + + self.initialize_weights() + + def initialize_weights(self): + # Zero-out AdaLN layers in DiT blocks: + for block in self.transformer_blocks: + nn.init.constant_(block.attn_norm.linear.weight, 0) + nn.init.constant_(block.attn_norm.linear.bias, 0) + + # Zero-out output layers: + nn.init.constant_(self.norm_out.linear.weight, 0) + nn.init.constant_(self.norm_out.linear.bias, 0) + nn.init.constant_(self.proj_out.weight, 0) + nn.init.constant_(self.proj_out.bias, 0) + + def ckpt_wrapper(self, module): + # https://github.com/chuanyangjin/fast-DiT/blob/main/models.py + def ckpt_forward(*inputs): + outputs = module(*inputs) + return outputs + + return ckpt_forward + + def get_input_embed( + self, + x, # b n d + cond, # b n d + text, # b nt + drop_audio_cond: bool = False, + drop_text: bool = False, + cache: bool = True, + ): + seq_len = x.shape[1] + if cache: + if drop_text: + if self.text_uncond is None: + self.text_uncond = self.text_embed(text, seq_len, drop_text=True) + text_embed = self.text_uncond + else: + if self.text_cond is None: + self.text_cond = self.text_embed(text, seq_len, drop_text=False) + text_embed = self.text_cond + else: + text_embed = self.text_embed(text, seq_len, drop_text=drop_text) + + x = self.input_embed(x, cond, text_embed, drop_audio_cond=drop_audio_cond) + + return x + + def clear_cache(self): + self.text_cond, self.text_uncond = None, None + + def forward( + self, + x: float["b n d"], # nosied input audio # noqa: F722 + cond: float["b n d"], # masked cond audio # noqa: F722 + text: int["b nt"], # text # noqa: F722 + time: float["b"] | float[""], # time step # noqa: F821 F722 + mask: bool["b n"] | None = None, # noqa: F722 + drop_audio_cond: bool = False, # cfg for cond audio + drop_text: bool = False, # cfg for text + cfg_infer: bool = False, # cfg inference, pack cond & uncond forward + cache: bool = False, + ): + batch, seq_len = x.shape[0], x.shape[1] + if time.ndim == 0: + time = time.repeat(batch) + + # t: conditioning time, text: text, x: noised audio + cond audio + text + t = self.time_embed(time) + if cfg_infer: # pack cond & uncond forward: b n d -> 2b n d + x_cond = self.get_input_embed(x, cond, text, drop_audio_cond=False, drop_text=False, cache=cache) + x_uncond = self.get_input_embed(x, cond, text, drop_audio_cond=True, drop_text=True, cache=cache) + x = torch.cat((x_cond, x_uncond), dim=0) + t = torch.cat((t, t), dim=0) + mask = torch.cat((mask, mask), dim=0) if mask is not None else None + else: + x = self.get_input_embed(x, cond, text, drop_audio_cond=drop_audio_cond, drop_text=drop_text, cache=cache) + + rope = self.rotary_embed.forward_from_seq_len(seq_len) + + if self.long_skip_connection is not None: + residual = x + + for block in self.transformer_blocks: + if self.checkpoint_activations: + # https://pytorch.org/docs/stable/checkpoint.html#torch.utils.checkpoint.checkpoint + x = torch.utils.checkpoint.checkpoint(self.ckpt_wrapper(block), x, t, mask, rope, use_reentrant=False) + else: + x = block(x, t, mask=mask, rope=rope) + + if self.long_skip_connection is not None: + x = self.long_skip_connection(torch.cat((x, residual), dim=-1)) + + x = self.norm_out(x, t) + output = self.proj_out(x) + + return output diff --git a/src/f5_tts/src/f5_tts/model/backbones/mmdit.py b/src/f5_tts/src/f5_tts/model/backbones/mmdit.py new file mode 100644 index 0000000000000000000000000000000000000000..41ca0d5531faa7232f024dae968b5d704e32fda0 --- /dev/null +++ b/src/f5_tts/src/f5_tts/model/backbones/mmdit.py @@ -0,0 +1,212 @@ +""" +ein notation: +b - batch +n - sequence +nt - text sequence +nw - raw wave length +d - dimension +""" + +from __future__ import annotations + +import torch +from torch import nn +from x_transformers.x_transformers import RotaryEmbedding + +from f5_tts.model.modules import ( + AdaLayerNorm_Final, + ConvPositionEmbedding, + MMDiTBlock, + TimestepEmbedding, + get_pos_embed_indices, + precompute_freqs_cis, +) + + +# text embedding + + +class TextEmbedding(nn.Module): + def __init__(self, out_dim, text_num_embeds, mask_padding=True): + super().__init__() + self.text_embed = nn.Embedding(text_num_embeds + 1, out_dim) # will use 0 as filler token + + self.mask_padding = mask_padding # mask filler and batch padding tokens or not + + self.precompute_max_pos = 1024 + self.register_buffer("freqs_cis", precompute_freqs_cis(out_dim, self.precompute_max_pos), persistent=False) + + def forward(self, text: int["b nt"], drop_text=False) -> int["b nt d"]: # noqa: F722 + text = text + 1 # use 0 as filler token. preprocess of batch pad -1, see list_str_to_idx() + if self.mask_padding: + text_mask = text == 0 + + if drop_text: # cfg for text + text = torch.zeros_like(text) + + text = self.text_embed(text) # b nt -> b nt d + + # sinus pos emb + batch_start = torch.zeros((text.shape[0],), dtype=torch.long) + batch_text_len = text.shape[1] + pos_idx = get_pos_embed_indices(batch_start, batch_text_len, max_pos=self.precompute_max_pos) + text_pos_embed = self.freqs_cis[pos_idx] + + text = text + text_pos_embed + + if self.mask_padding: + text = text.masked_fill(text_mask.unsqueeze(-1).expand(-1, -1, text.size(-1)), 0.0) + + return text + + +# noised input & masked cond audio embedding + + +class AudioEmbedding(nn.Module): + def __init__(self, in_dim, out_dim): + super().__init__() + self.linear = nn.Linear(2 * in_dim, out_dim) + self.conv_pos_embed = ConvPositionEmbedding(out_dim) + + def forward(self, x: float["b n d"], cond: float["b n d"], drop_audio_cond=False): # noqa: F722 + if drop_audio_cond: + cond = torch.zeros_like(cond) + x = torch.cat((x, cond), dim=-1) + x = self.linear(x) + x = self.conv_pos_embed(x) + x + return x + + +# Transformer backbone using MM-DiT blocks + + +class MMDiT(nn.Module): + def __init__( + self, + *, + dim, + depth=8, + heads=8, + dim_head=64, + dropout=0.1, + ff_mult=4, + mel_dim=100, + text_num_embeds=256, + text_mask_padding=True, + qk_norm=None, + ): + super().__init__() + + self.time_embed = TimestepEmbedding(dim) + self.text_embed = TextEmbedding(dim, text_num_embeds, mask_padding=text_mask_padding) + self.text_cond, self.text_uncond = None, None # text cache + self.audio_embed = AudioEmbedding(mel_dim, dim) + + self.rotary_embed = RotaryEmbedding(dim_head) + + self.dim = dim + self.depth = depth + + self.transformer_blocks = nn.ModuleList( + [ + MMDiTBlock( + dim=dim, + heads=heads, + dim_head=dim_head, + dropout=dropout, + ff_mult=ff_mult, + context_pre_only=i == depth - 1, + qk_norm=qk_norm, + ) + for i in range(depth) + ] + ) + self.norm_out = AdaLayerNorm_Final(dim) # final modulation + self.proj_out = nn.Linear(dim, mel_dim) + + self.initialize_weights() + + def initialize_weights(self): + # Zero-out AdaLN layers in MMDiT blocks: + for block in self.transformer_blocks: + nn.init.constant_(block.attn_norm_x.linear.weight, 0) + nn.init.constant_(block.attn_norm_x.linear.bias, 0) + nn.init.constant_(block.attn_norm_c.linear.weight, 0) + nn.init.constant_(block.attn_norm_c.linear.bias, 0) + + # Zero-out output layers: + nn.init.constant_(self.norm_out.linear.weight, 0) + nn.init.constant_(self.norm_out.linear.bias, 0) + nn.init.constant_(self.proj_out.weight, 0) + nn.init.constant_(self.proj_out.bias, 0) + + def get_input_embed( + self, + x, # b n d + cond, # b n d + text, # b nt + drop_audio_cond: bool = False, + drop_text: bool = False, + cache: bool = True, + ): + if cache: + if drop_text: + if self.text_uncond is None: + self.text_uncond = self.text_embed(text, drop_text=True) + c = self.text_uncond + else: + if self.text_cond is None: + self.text_cond = self.text_embed(text, drop_text=False) + c = self.text_cond + else: + c = self.text_embed(text, drop_text=drop_text) + x = self.audio_embed(x, cond, drop_audio_cond=drop_audio_cond) + + return x, c + + def clear_cache(self): + self.text_cond, self.text_uncond = None, None + + def forward( + self, + x: float["b n d"], # nosied input audio # noqa: F722 + cond: float["b n d"], # masked cond audio # noqa: F722 + text: int["b nt"], # text # noqa: F722 + time: float["b"] | float[""], # time step # noqa: F821 F722 + mask: bool["b n"] | None = None, # noqa: F722 + drop_audio_cond: bool = False, # cfg for cond audio + drop_text: bool = False, # cfg for text + cfg_infer: bool = False, # cfg inference, pack cond & uncond forward + cache: bool = False, + ): + batch = x.shape[0] + if time.ndim == 0: + time = time.repeat(batch) + + # t: conditioning (time), c: context (text + masked cond audio), x: noised input audio + t = self.time_embed(time) + if cfg_infer: # pack cond & uncond forward: b n d -> 2b n d + x_cond, c_cond = self.get_input_embed(x, cond, text, drop_audio_cond=False, drop_text=False, cache=cache) + x_uncond, c_uncond = self.get_input_embed(x, cond, text, drop_audio_cond=True, drop_text=True, cache=cache) + x = torch.cat((x_cond, x_uncond), dim=0) + c = torch.cat((c_cond, c_uncond), dim=0) + t = torch.cat((t, t), dim=0) + mask = torch.cat((mask, mask), dim=0) if mask is not None else None + else: + x, c = self.get_input_embed( + x, cond, text, drop_audio_cond=drop_audio_cond, drop_text=drop_text, cache=cache + ) + + seq_len = x.shape[1] + text_len = text.shape[1] + rope_audio = self.rotary_embed.forward_from_seq_len(seq_len) + rope_text = self.rotary_embed.forward_from_seq_len(text_len) + + for block in self.transformer_blocks: + c, x = block(x, c, t, mask=mask, rope=rope_audio, c_rope=rope_text) + + x = self.norm_out(x, t) + output = self.proj_out(x) + + return output diff --git a/src/f5_tts/src/f5_tts/model/backbones/unett.py b/src/f5_tts/src/f5_tts/model/backbones/unett.py new file mode 100644 index 0000000000000000000000000000000000000000..ee4812e2205f0649cdffe3c1b76f927078940330 --- /dev/null +++ b/src/f5_tts/src/f5_tts/model/backbones/unett.py @@ -0,0 +1,273 @@ +""" +ein notation: +b - batch +n - sequence +nt - text sequence +nw - raw wave length +d - dimension +""" + +from __future__ import annotations + +from typing import Literal + +import torch +import torch.nn.functional as F +from torch import nn +from x_transformers import RMSNorm +from x_transformers.x_transformers import RotaryEmbedding + +from f5_tts.model.modules import ( + Attention, + AttnProcessor, + ConvNeXtV2Block, + ConvPositionEmbedding, + FeedForward, + TimestepEmbedding, + get_pos_embed_indices, + precompute_freqs_cis, +) + + +# Text embedding + + +class TextEmbedding(nn.Module): + def __init__(self, text_num_embeds, text_dim, mask_padding=True, conv_layers=0, conv_mult=2): + super().__init__() + self.text_embed = nn.Embedding(text_num_embeds + 1, text_dim) # use 0 as filler token + + self.mask_padding = mask_padding # mask filler and batch padding tokens or not + + if conv_layers > 0: + self.extra_modeling = True + self.precompute_max_pos = 4096 # ~44s of 24khz audio + self.register_buffer("freqs_cis", precompute_freqs_cis(text_dim, self.precompute_max_pos), persistent=False) + self.text_blocks = nn.Sequential( + *[ConvNeXtV2Block(text_dim, text_dim * conv_mult) for _ in range(conv_layers)] + ) + else: + self.extra_modeling = False + + def forward(self, text: int["b nt"], seq_len, drop_text=False): # noqa: F722 + text = text + 1 # use 0 as filler token. preprocess of batch pad -1, see list_str_to_idx() + text = text[:, :seq_len] # curtail if character tokens are more than the mel spec tokens + batch, text_len = text.shape[0], text.shape[1] + text = F.pad(text, (0, seq_len - text_len), value=0) + if self.mask_padding: + text_mask = text == 0 + + if drop_text: # cfg for text + text = torch.zeros_like(text) + + text = self.text_embed(text) # b n -> b n d + + # possible extra modeling + if self.extra_modeling: + # sinus pos emb + batch_start = torch.zeros((batch,), dtype=torch.long) + pos_idx = get_pos_embed_indices(batch_start, seq_len, max_pos=self.precompute_max_pos) + text_pos_embed = self.freqs_cis[pos_idx] + text = text + text_pos_embed + + # convnextv2 blocks + if self.mask_padding: + text = text.masked_fill(text_mask.unsqueeze(-1).expand(-1, -1, text.size(-1)), 0.0) + for block in self.text_blocks: + text = block(text) + text = text.masked_fill(text_mask.unsqueeze(-1).expand(-1, -1, text.size(-1)), 0.0) + else: + text = self.text_blocks(text) + + return text + + +# noised input audio and context mixing embedding + + +class InputEmbedding(nn.Module): + def __init__(self, mel_dim, text_dim, out_dim): + super().__init__() + self.proj = nn.Linear(mel_dim * 2 + text_dim, out_dim) + self.conv_pos_embed = ConvPositionEmbedding(dim=out_dim) + + def forward(self, x: float["b n d"], cond: float["b n d"], text_embed: float["b n d"], drop_audio_cond=False): # noqa: F722 + if drop_audio_cond: # cfg for cond audio + cond = torch.zeros_like(cond) + + x = self.proj(torch.cat((x, cond, text_embed), dim=-1)) + x = self.conv_pos_embed(x) + x + return x + + +# Flat UNet Transformer backbone + + +class UNetT(nn.Module): + def __init__( + self, + *, + dim, + depth=8, + heads=8, + dim_head=64, + dropout=0.1, + ff_mult=4, + mel_dim=100, + text_num_embeds=256, + text_dim=None, + text_mask_padding=True, + qk_norm=None, + conv_layers=0, + pe_attn_head=None, + skip_connect_type: Literal["add", "concat", "none"] = "concat", + ): + super().__init__() + assert depth % 2 == 0, "UNet-Transformer's depth should be even." + + self.time_embed = TimestepEmbedding(dim) + if text_dim is None: + text_dim = mel_dim + self.text_embed = TextEmbedding( + text_num_embeds, text_dim, mask_padding=text_mask_padding, conv_layers=conv_layers + ) + self.text_cond, self.text_uncond = None, None # text cache + self.input_embed = InputEmbedding(mel_dim, text_dim, dim) + + self.rotary_embed = RotaryEmbedding(dim_head) + + # transformer layers & skip connections + + self.dim = dim + self.skip_connect_type = skip_connect_type + needs_skip_proj = skip_connect_type == "concat" + + self.depth = depth + self.layers = nn.ModuleList([]) + + for idx in range(depth): + is_later_half = idx >= (depth // 2) + + attn_norm = RMSNorm(dim) + attn = Attention( + processor=AttnProcessor(pe_attn_head=pe_attn_head), + dim=dim, + heads=heads, + dim_head=dim_head, + dropout=dropout, + qk_norm=qk_norm, + ) + + ff_norm = RMSNorm(dim) + ff = FeedForward(dim=dim, mult=ff_mult, dropout=dropout, approximate="tanh") + + skip_proj = nn.Linear(dim * 2, dim, bias=False) if needs_skip_proj and is_later_half else None + + self.layers.append( + nn.ModuleList( + [ + skip_proj, + attn_norm, + attn, + ff_norm, + ff, + ] + ) + ) + + self.norm_out = RMSNorm(dim) + self.proj_out = nn.Linear(dim, mel_dim) + + def get_input_embed( + self, + x, # b n d + cond, # b n d + text, # b nt + drop_audio_cond: bool = False, + drop_text: bool = False, + cache: bool = True, + ): + seq_len = x.shape[1] + if cache: + if drop_text: + if self.text_uncond is None: + self.text_uncond = self.text_embed(text, seq_len, drop_text=True) + text_embed = self.text_uncond + else: + if self.text_cond is None: + self.text_cond = self.text_embed(text, seq_len, drop_text=False) + text_embed = self.text_cond + else: + text_embed = self.text_embed(text, seq_len, drop_text=drop_text) + + x = self.input_embed(x, cond, text_embed, drop_audio_cond=drop_audio_cond) + + return x + + def clear_cache(self): + self.text_cond, self.text_uncond = None, None + + def forward( + self, + x: float["b n d"], # nosied input audio # noqa: F722 + cond: float["b n d"], # masked cond audio # noqa: F722 + text: int["b nt"], # text # noqa: F722 + time: float["b"] | float[""], # time step # noqa: F821 F722 + mask: bool["b n"] | None = None, # noqa: F722 + drop_audio_cond: bool = False, # cfg for cond audio + drop_text: bool = False, # cfg for text + cfg_infer: bool = False, # cfg inference, pack cond & uncond forward + cache: bool = False, + ): + batch, seq_len = x.shape[0], x.shape[1] + if time.ndim == 0: + time = time.repeat(batch) + + # t: conditioning time, c: context (text + masked cond audio), x: noised input audio + t = self.time_embed(time) + if cfg_infer: # pack cond & uncond forward: b n d -> 2b n d + x_cond = self.get_input_embed(x, cond, text, drop_audio_cond=False, drop_text=False, cache=cache) + x_uncond = self.get_input_embed(x, cond, text, drop_audio_cond=True, drop_text=True, cache=cache) + x = torch.cat((x_cond, x_uncond), dim=0) + t = torch.cat((t, t), dim=0) + mask = torch.cat((mask, mask), dim=0) if mask is not None else None + else: + x = self.get_input_embed(x, cond, text, drop_audio_cond=drop_audio_cond, drop_text=drop_text, cache=cache) + + # postfix time t to input x, [b n d] -> [b n+1 d] + x = torch.cat([t.unsqueeze(1), x], dim=1) # pack t to x + if mask is not None: + mask = F.pad(mask, (1, 0), value=1) + + rope = self.rotary_embed.forward_from_seq_len(seq_len + 1) + + # flat unet transformer + skip_connect_type = self.skip_connect_type + skips = [] + for idx, (maybe_skip_proj, attn_norm, attn, ff_norm, ff) in enumerate(self.layers): + layer = idx + 1 + + # skip connection logic + is_first_half = layer <= (self.depth // 2) + is_later_half = not is_first_half + + if is_first_half: + skips.append(x) + + if is_later_half: + skip = skips.pop() + if skip_connect_type == "concat": + x = torch.cat((x, skip), dim=-1) + x = maybe_skip_proj(x) + elif skip_connect_type == "add": + x = x + skip + + # attention and feedforward blocks + x = attn(attn_norm(x), rope=rope, mask=mask) + x + x = ff(ff_norm(x)) + x + + assert len(skips) == 0 + + x = self.norm_out(x)[:, 1:, :] # unpack t from x + + return self.proj_out(x) diff --git a/src/f5_tts/src/f5_tts/model/cfm.py b/src/f5_tts/src/f5_tts/model/cfm.py new file mode 100644 index 0000000000000000000000000000000000000000..02d88f097a147e34772b88c7e7d1f82fc3274a62 --- /dev/null +++ b/src/f5_tts/src/f5_tts/model/cfm.py @@ -0,0 +1,302 @@ +""" +ein notation: +b - batch +n - sequence +nt - text sequence +nw - raw wave length +d - dimension +""" + +from __future__ import annotations + +from random import random +from typing import Callable + +import torch +import torch.nn.functional as F +from torch import nn +from torch.nn.utils.rnn import pad_sequence +from torchdiffeq import odeint + +from f5_tts.model.modules import MelSpec +from f5_tts.model.utils import ( + default, + exists, + get_epss_timesteps, + lens_to_mask, + list_str_to_idx, + list_str_to_tensor, + mask_from_frac_lengths, +) + + +class CFM(nn.Module): + def __init__( + self, + transformer: nn.Module, + sigma=0.0, + odeint_kwargs: dict = dict( + # atol = 1e-5, + # rtol = 1e-5, + method="euler" # 'midpoint' + ), + audio_drop_prob=0.3, + cond_drop_prob=0.2, + num_channels=None, + mel_spec_module: nn.Module | None = None, + mel_spec_kwargs: dict = dict(), + frac_lengths_mask: tuple[float, float] = (0.7, 1.0), + vocab_char_map: dict[str:int] | None = None, + ): + super().__init__() + + self.frac_lengths_mask = frac_lengths_mask + + # mel spec + self.mel_spec = default(mel_spec_module, MelSpec(**mel_spec_kwargs)) + num_channels = default(num_channels, self.mel_spec.n_mel_channels) + self.num_channels = num_channels + + # classifier-free guidance + self.audio_drop_prob = audio_drop_prob + self.cond_drop_prob = cond_drop_prob + + # transformer + self.transformer = transformer + dim = transformer.dim + self.dim = dim + + # conditional flow related + self.sigma = sigma + + # sampling related + self.odeint_kwargs = odeint_kwargs + + # vocab map for tokenization + self.vocab_char_map = vocab_char_map + + @property + def device(self): + return next(self.parameters()).device + + @torch.no_grad() + def sample( + self, + cond: float["b n d"] | float["b nw"], # noqa: F722 + text: int["b nt"] | list[str], # noqa: F722 + duration: int | int["b"], # noqa: F821 + *, + lens: int["b"] | None = None, # noqa: F821 + steps=32, + cfg_strength=1.0, + sway_sampling_coef=None, + seed: int | None = None, + max_duration=4096, + vocoder: Callable[[float["b d n"]], float["b nw"]] | None = None, # noqa: F722 + use_epss=True, + no_ref_audio=False, + duplicate_test=False, + t_inter=0.1, + edit_mask=None, + ): + self.eval() + # raw wave + + if cond.ndim == 2: + cond = self.mel_spec(cond) + cond = cond.permute(0, 2, 1) + assert cond.shape[-1] == self.num_channels + + cond = cond.to(next(self.parameters()).dtype) + + batch, cond_seq_len, device = *cond.shape[:2], cond.device + if not exists(lens): + lens = torch.full((batch,), cond_seq_len, device=device, dtype=torch.long) + + # text + + if isinstance(text, list): + if exists(self.vocab_char_map): + text = list_str_to_idx(text, self.vocab_char_map).to(device) + else: + text = list_str_to_tensor(text).to(device) + assert text.shape[0] == batch + + # duration + + cond_mask = lens_to_mask(lens) + if edit_mask is not None: + cond_mask = cond_mask & edit_mask + + if isinstance(duration, int): + duration = torch.full((batch,), duration, device=device, dtype=torch.long) + + duration = torch.maximum( + torch.maximum((text != -1).sum(dim=-1), lens) + 1, duration + ) # duration at least text/audio prompt length plus one token, so something is generated + duration = duration.clamp(max=max_duration) + max_duration = duration.amax() + + # duplicate test corner for inner time step oberservation + if duplicate_test: + test_cond = F.pad(cond, (0, 0, cond_seq_len, max_duration - 2 * cond_seq_len), value=0.0) + + cond = F.pad(cond, (0, 0, 0, max_duration - cond_seq_len), value=0.0) + if no_ref_audio: + cond = torch.zeros_like(cond) + + cond_mask = F.pad(cond_mask, (0, max_duration - cond_mask.shape[-1]), value=False) + cond_mask = cond_mask.unsqueeze(-1) + step_cond = torch.where( + cond_mask, cond, torch.zeros_like(cond) + ) # allow direct control (cut cond audio) with lens passed in + + if batch > 1: + mask = lens_to_mask(duration) + else: # save memory and speed up, as single inference need no mask currently + mask = None + + # neural ode + + def fn(t, x): + # at each step, conditioning is fixed + # step_cond = torch.where(cond_mask, cond, torch.zeros_like(cond)) + + # predict flow (cond) + if cfg_strength < 1e-5: + pred = self.transformer( + x=x, + cond=step_cond, + text=text, + time=t, + mask=mask, + drop_audio_cond=False, + drop_text=False, + cache=True, + ) + return pred + + # predict flow (cond and uncond), for classifier-free guidance + pred_cfg = self.transformer( + x=x, + cond=step_cond, + text=text, + time=t, + mask=mask, + cfg_infer=True, + cache=True, + ) + pred, null_pred = torch.chunk(pred_cfg, 2, dim=0) + return pred + (pred - null_pred) * cfg_strength + + # noise input + # to make sure batch inference result is same with different batch size, and for sure single inference + # still some difference maybe due to convolutional layers + y0 = [] + for dur in duration: + if exists(seed): + torch.manual_seed(seed) + y0.append(torch.randn(dur, self.num_channels, device=self.device, dtype=step_cond.dtype)) + y0 = pad_sequence(y0, padding_value=0, batch_first=True) + + t_start = 0 + + # duplicate test corner for inner time step oberservation + if duplicate_test: + t_start = t_inter + y0 = (1 - t_start) * y0 + t_start * test_cond + steps = int(steps * (1 - t_start)) + + if t_start == 0 and use_epss: # use Empirically Pruned Step Sampling for low NFE + t = get_epss_timesteps(steps, device=self.device, dtype=step_cond.dtype) + else: + t = torch.linspace(t_start, 1, steps + 1, device=self.device, dtype=step_cond.dtype) + if sway_sampling_coef is not None: + t = t + sway_sampling_coef * (torch.cos(torch.pi / 2 * t) - 1 + t) + + trajectory = odeint(fn, y0, t, **self.odeint_kwargs) + self.transformer.clear_cache() + + sampled = trajectory[-1] + out = sampled + out = torch.where(cond_mask, cond, out) + + if exists(vocoder): + out = out.permute(0, 2, 1) + out = vocoder(out) + + return out, trajectory + + def forward( + self, + inp: float["b n d"] | float["b nw"], # mel or raw wave # noqa: F722 + text: int["b nt"] | list[str], # noqa: F722 + *, + lens: int["b"] | None = None, # noqa: F821 + noise_scheduler: str | None = None, + ): + # handle raw wave + if inp.ndim == 2: + inp = self.mel_spec(inp) + inp = inp.permute(0, 2, 1) + assert inp.shape[-1] == self.num_channels + + batch, seq_len, dtype, device, _σ1 = *inp.shape[:2], inp.dtype, self.device, self.sigma + + # handle text as string + if isinstance(text, list): + if exists(self.vocab_char_map): + text = list_str_to_idx(text, self.vocab_char_map).to(device) + else: + text = list_str_to_tensor(text).to(device) + assert text.shape[0] == batch + + # lens and mask + if not exists(lens): + lens = torch.full((batch,), seq_len, device=device) + + mask = lens_to_mask(lens, length=seq_len) # useless here, as collate_fn will pad to max length in batch + + # get a random span to mask out for training conditionally + frac_lengths = torch.zeros((batch,), device=self.device).float().uniform_(*self.frac_lengths_mask) + rand_span_mask = mask_from_frac_lengths(lens, frac_lengths) + + if exists(mask): + rand_span_mask &= mask + + # mel is x1 + x1 = inp + + # x0 is gaussian noise + x0 = torch.randn_like(x1) + + # time step + time = torch.rand((batch,), dtype=dtype, device=self.device) + # TODO. noise_scheduler + + # sample xt (φ_t(x) in the paper) + t = time.unsqueeze(-1).unsqueeze(-1) + φ = (1 - t) * x0 + t * x1 + flow = x1 - x0 + + # only predict what is within the random mask span for infilling + cond = torch.where(rand_span_mask[..., None], torch.zeros_like(x1), x1) + + # transformer and cfg training with a drop rate + drop_audio_cond = random() < self.audio_drop_prob # p_drop in voicebox paper + if random() < self.cond_drop_prob: # p_uncond in voicebox paper + drop_audio_cond = True + drop_text = True + else: + drop_text = False + + # apply mask will use more memory; might adjust batchsize or batchsampler long sequence threshold + pred = self.transformer( + x=φ, cond=cond, text=text, time=time, drop_audio_cond=drop_audio_cond, drop_text=drop_text, mask=mask + ) + + # flow matching loss + loss = F.mse_loss(pred, flow, reduction="none") + loss = loss[rand_span_mask] + + return loss.mean(), cond, pred diff --git a/src/f5_tts/src/f5_tts/model/dataset.py b/src/f5_tts/src/f5_tts/model/dataset.py new file mode 100644 index 0000000000000000000000000000000000000000..de0d9ad32a436427539be20b9c4160e2985d4325 --- /dev/null +++ b/src/f5_tts/src/f5_tts/model/dataset.py @@ -0,0 +1,330 @@ +import json +from importlib.resources import files + +import torch +import torch.nn.functional as F +import torchaudio +from datasets import Dataset as Dataset_ +from datasets import load_from_disk +from torch import nn +from torch.utils.data import Dataset, Sampler +from tqdm import tqdm + +from f5_tts.model.modules import MelSpec +from f5_tts.model.utils import default + + +class HFDataset(Dataset): + def __init__( + self, + hf_dataset: Dataset, + target_sample_rate=24_000, + n_mel_channels=100, + hop_length=256, + n_fft=1024, + win_length=1024, + mel_spec_type="vocos", + ): + self.data = hf_dataset + self.target_sample_rate = target_sample_rate + self.hop_length = hop_length + + self.mel_spectrogram = MelSpec( + n_fft=n_fft, + hop_length=hop_length, + win_length=win_length, + n_mel_channels=n_mel_channels, + target_sample_rate=target_sample_rate, + mel_spec_type=mel_spec_type, + ) + + def get_frame_len(self, index): + row = self.data[index] + audio = row["audio"]["array"] + sample_rate = row["audio"]["sampling_rate"] + return audio.shape[-1] / sample_rate * self.target_sample_rate / self.hop_length + + def __len__(self): + return len(self.data) + + def __getitem__(self, index): + row = self.data[index] + audio = row["audio"]["array"] + + # logger.info(f"Audio shape: {audio.shape}") + + sample_rate = row["audio"]["sampling_rate"] + duration = audio.shape[-1] / sample_rate + + if duration > 30 or duration < 0.3: + return self.__getitem__((index + 1) % len(self.data)) + + audio_tensor = torch.from_numpy(audio).float() + + if sample_rate != self.target_sample_rate: + resampler = torchaudio.transforms.Resample(sample_rate, self.target_sample_rate) + audio_tensor = resampler(audio_tensor) + + audio_tensor = audio_tensor.unsqueeze(0) # 't -> 1 t') + + mel_spec = self.mel_spectrogram(audio_tensor) + + mel_spec = mel_spec.squeeze(0) # '1 d t -> d t' + + text = row["text"] + + return dict( + mel_spec=mel_spec, + text=text, + ) + + +class CustomDataset(Dataset): + def __init__( + self, + custom_dataset: Dataset, + durations=None, + target_sample_rate=24_000, + hop_length=256, + n_mel_channels=100, + n_fft=1024, + win_length=1024, + mel_spec_type="vocos", + preprocessed_mel=False, + mel_spec_module: nn.Module | None = None, + ): + self.data = custom_dataset + self.durations = durations + self.target_sample_rate = target_sample_rate + self.hop_length = hop_length + self.n_fft = n_fft + self.win_length = win_length + self.mel_spec_type = mel_spec_type + self.preprocessed_mel = preprocessed_mel + + if not preprocessed_mel: + self.mel_spectrogram = default( + mel_spec_module, + MelSpec( + n_fft=n_fft, + hop_length=hop_length, + win_length=win_length, + n_mel_channels=n_mel_channels, + target_sample_rate=target_sample_rate, + mel_spec_type=mel_spec_type, + ), + ) + + def get_frame_len(self, index): + if ( + self.durations is not None + ): # Please make sure the separately provided durations are correct, otherwise 99.99% OOM + return self.durations[index] * self.target_sample_rate / self.hop_length + return self.data[index]["duration"] * self.target_sample_rate / self.hop_length + + def __len__(self): + return len(self.data) + + def __getitem__(self, index): + while True: + row = self.data[index] + audio_path = row["audio_path"] + text = row["text"] + duration = row["duration"] + + # filter by given length + if 0.3 <= duration <= 30: + break # valid + + index = (index + 1) % len(self.data) + + if self.preprocessed_mel: + mel_spec = torch.tensor(row["mel_spec"]) + else: + audio, source_sample_rate = torchaudio.load(audio_path) + + # make sure mono input + if audio.shape[0] > 1: + audio = torch.mean(audio, dim=0, keepdim=True) + + # resample if necessary + if source_sample_rate != self.target_sample_rate: + resampler = torchaudio.transforms.Resample(source_sample_rate, self.target_sample_rate) + audio = resampler(audio) + + # to mel spectrogram + mel_spec = self.mel_spectrogram(audio) + mel_spec = mel_spec.squeeze(0) # '1 d t -> d t' + + return { + "mel_spec": mel_spec, + "text": text, + } + + +# Dynamic Batch Sampler +class DynamicBatchSampler(Sampler[list[int]]): + """Extension of Sampler that will do the following: + 1. Change the batch size (essentially number of sequences) + in a batch to ensure that the total number of frames are less + than a certain threshold. + 2. Make sure the padding efficiency in the batch is high. + 3. Shuffle batches each epoch while maintaining reproducibility. + """ + + def __init__( + self, sampler: Sampler[int], frames_threshold: int, max_samples=0, random_seed=None, drop_residual: bool = False + ): + self.sampler = sampler + self.frames_threshold = frames_threshold + self.max_samples = max_samples + self.random_seed = random_seed + self.epoch = 0 + + indices, batches = [], [] + data_source = self.sampler.data_source + + for idx in tqdm( + self.sampler, desc="Sorting with sampler... if slow, check whether dataset is provided with duration" + ): + indices.append((idx, data_source.get_frame_len(idx))) + indices.sort(key=lambda elem: elem[1]) + + batch = [] + batch_frames = 0 + for idx, frame_len in tqdm( + indices, desc=f"Creating dynamic batches with {frames_threshold} audio frames per gpu" + ): + if batch_frames + frame_len <= self.frames_threshold and (max_samples == 0 or len(batch) < max_samples): + batch.append(idx) + batch_frames += frame_len + else: + if len(batch) > 0: + batches.append(batch) + if frame_len <= self.frames_threshold: + batch = [idx] + batch_frames = frame_len + else: + batch = [] + batch_frames = 0 + + if not drop_residual and len(batch) > 0: + batches.append(batch) + + del indices + self.batches = batches + + # Ensure even batches with accelerate BatchSamplerShard cls under frame_per_batch setting + self.drop_last = True + + def set_epoch(self, epoch: int) -> None: + """Sets the epoch for this sampler.""" + self.epoch = epoch + + def __iter__(self): + # Use both random_seed and epoch for deterministic but different shuffling per epoch + if self.random_seed is not None: + g = torch.Generator() + g.manual_seed(self.random_seed + self.epoch) + # Use PyTorch's random permutation for better reproducibility across PyTorch versions + indices = torch.randperm(len(self.batches), generator=g).tolist() + batches = [self.batches[i] for i in indices] + else: + batches = self.batches + return iter(batches) + + def __len__(self): + return len(self.batches) + + +# Load dataset + + +def load_dataset( + dataset_name: str, + tokenizer: str = "pinyin", + dataset_type: str = "CustomDataset", + audio_type: str = "raw", + mel_spec_module: nn.Module | None = None, + mel_spec_kwargs: dict = dict(), +) -> CustomDataset | HFDataset: + """ + dataset_type - "CustomDataset" if you want to use tokenizer name and default data path to load for train_dataset + - "CustomDatasetPath" if you just want to pass the full path to a preprocessed dataset without relying on tokenizer + """ + + print("Loading dataset ...") + + if dataset_type == "CustomDataset": + rel_data_path = str(files("f5_tts").joinpath(f"../../data/{dataset_name}_{tokenizer}")) + if audio_type == "raw": + try: + train_dataset = load_from_disk(f"{rel_data_path}/raw") + except: # noqa: E722 + train_dataset = Dataset_.from_file(f"{rel_data_path}/raw.arrow") + preprocessed_mel = False + elif audio_type == "mel": + train_dataset = Dataset_.from_file(f"{rel_data_path}/mel.arrow") + preprocessed_mel = True + with open(f"{rel_data_path}/duration.json", "r", encoding="utf-8") as f: + data_dict = json.load(f) + durations = data_dict["duration"] + train_dataset = CustomDataset( + train_dataset, + durations=durations, + preprocessed_mel=preprocessed_mel, + mel_spec_module=mel_spec_module, + **mel_spec_kwargs, + ) + + elif dataset_type == "CustomDatasetPath": + try: + train_dataset = load_from_disk(f"{dataset_name}/raw") + except: # noqa: E722 + train_dataset = Dataset_.from_file(f"{dataset_name}/raw.arrow") + + with open(f"{dataset_name}/duration.json", "r", encoding="utf-8") as f: + data_dict = json.load(f) + durations = data_dict["duration"] + train_dataset = CustomDataset( + train_dataset, durations=durations, preprocessed_mel=preprocessed_mel, **mel_spec_kwargs + ) + + elif dataset_type == "HFDataset": + print( + "Should manually modify the path of huggingface dataset to your need.\n" + + "May also the corresponding script cuz different dataset may have different format." + ) + pre, post = dataset_name.split("_") + train_dataset = HFDataset( + load_dataset(f"{pre}/{pre}", split=f"train.{post}", cache_dir=str(files("f5_tts").joinpath("../../data"))), + ) + + return train_dataset + + +# collation + + +def collate_fn(batch): + mel_specs = [item["mel_spec"].squeeze(0) for item in batch] + mel_lengths = torch.LongTensor([spec.shape[-1] for spec in mel_specs]) + max_mel_length = mel_lengths.amax() + + padded_mel_specs = [] + for spec in mel_specs: + padding = (0, max_mel_length - spec.size(-1)) + padded_spec = F.pad(spec, padding, value=0) + padded_mel_specs.append(padded_spec) + + mel_specs = torch.stack(padded_mel_specs) + + text = [item["text"] for item in batch] + text_lengths = torch.LongTensor([len(item) for item in text]) + + return dict( + mel=mel_specs, + mel_lengths=mel_lengths, # records for padding mask + text=text, + text_lengths=text_lengths, + ) diff --git a/src/f5_tts/src/f5_tts/model/modules.py b/src/f5_tts/src/f5_tts/model/modules.py new file mode 100644 index 0000000000000000000000000000000000000000..e2a5b8299ac79e149e4253d70a32310597f3aa0f --- /dev/null +++ b/src/f5_tts/src/f5_tts/model/modules.py @@ -0,0 +1,784 @@ +""" +ein notation: +b - batch +n - sequence +nt - text sequence +nw - raw wave length +d - dimension +""" +# flake8: noqa + +from __future__ import annotations + +import math +from typing import Optional + +import torch +import torch.nn.functional as F +import torchaudio +from librosa.filters import mel as librosa_mel_fn +from torch import nn +from x_transformers.x_transformers import apply_rotary_pos_emb + +from f5_tts.model.utils import is_package_available + + +# raw wav to mel spec + + +mel_basis_cache = {} +hann_window_cache = {} + + +def get_bigvgan_mel_spectrogram( + waveform, + n_fft=1024, + n_mel_channels=100, + target_sample_rate=24000, + hop_length=256, + win_length=1024, + fmin=0, + fmax=None, + center=False, +): # Copy from https://github.com/NVIDIA/BigVGAN/tree/main + device = waveform.device + key = f"{n_fft}_{n_mel_channels}_{target_sample_rate}_{hop_length}_{win_length}_{fmin}_{fmax}_{device}" + + if key not in mel_basis_cache: + mel = librosa_mel_fn(sr=target_sample_rate, n_fft=n_fft, n_mels=n_mel_channels, fmin=fmin, fmax=fmax) + mel_basis_cache[key] = torch.from_numpy(mel).float().to(device) # TODO: why they need .float()? + hann_window_cache[key] = torch.hann_window(win_length).to(device) + + mel_basis = mel_basis_cache[key] + hann_window = hann_window_cache[key] + + padding = (n_fft - hop_length) // 2 + waveform = torch.nn.functional.pad(waveform.unsqueeze(1), (padding, padding), mode="reflect").squeeze(1) + + spec = torch.stft( + waveform, + n_fft, + hop_length=hop_length, + win_length=win_length, + window=hann_window, + center=center, + pad_mode="reflect", + normalized=False, + onesided=True, + return_complex=True, + ) + spec = torch.sqrt(torch.view_as_real(spec).pow(2).sum(-1) + 1e-9) + + mel_spec = torch.matmul(mel_basis, spec) + mel_spec = torch.log(torch.clamp(mel_spec, min=1e-5)) + + return mel_spec + + +def get_vocos_mel_spectrogram( + waveform, + n_fft=1024, + n_mel_channels=100, + target_sample_rate=24000, + hop_length=256, + win_length=1024, +): + mel_stft = torchaudio.transforms.MelSpectrogram( + sample_rate=target_sample_rate, + n_fft=n_fft, + win_length=win_length, + hop_length=hop_length, + n_mels=n_mel_channels, + power=1, + center=True, + normalized=False, + norm=None, + ).to(waveform.device) + if len(waveform.shape) == 3: + waveform = waveform.squeeze(1) # 'b 1 nw -> b nw' + + assert len(waveform.shape) == 2 + + mel = mel_stft(waveform) + mel = mel.clamp(min=1e-5).log() + return mel + + +class MelSpec(nn.Module): + def __init__( + self, + n_fft=1024, + hop_length=256, + win_length=1024, + n_mel_channels=100, + target_sample_rate=24_000, + mel_spec_type="vocos", + ): + super().__init__() + assert mel_spec_type in ["vocos", "bigvgan"], print("We only support two extract mel backend: vocos or bigvgan") + + self.n_fft = n_fft + self.hop_length = hop_length + self.win_length = win_length + self.n_mel_channels = n_mel_channels + self.target_sample_rate = target_sample_rate + + if mel_spec_type == "vocos": + self.extractor = get_vocos_mel_spectrogram + elif mel_spec_type == "bigvgan": + self.extractor = get_bigvgan_mel_spectrogram + + self.register_buffer("dummy", torch.tensor(0), persistent=False) + + def forward(self, wav): + if self.dummy.device != wav.device: + self.to(wav.device) + + mel = self.extractor( + waveform=wav, + n_fft=self.n_fft, + n_mel_channels=self.n_mel_channels, + target_sample_rate=self.target_sample_rate, + hop_length=self.hop_length, + win_length=self.win_length, + ) + + return mel + + +# sinusoidal position embedding + + +class SinusPositionEmbedding(nn.Module): + def __init__(self, dim): + super().__init__() + self.dim = dim + + def forward(self, x, scale=1000): + device = x.device + half_dim = self.dim // 2 + emb = math.log(10000) / (half_dim - 1) + emb = torch.exp(torch.arange(half_dim, device=device).float() * -emb) + emb = scale * x.unsqueeze(1) * emb.unsqueeze(0) + emb = torch.cat((emb.sin(), emb.cos()), dim=-1) + return emb + + +# convolutional position embedding + + +class ConvPositionEmbedding(nn.Module): + def __init__(self, dim, kernel_size=31, groups=16): + super().__init__() + assert kernel_size % 2 != 0 + self.conv1d = nn.Sequential( + nn.Conv1d(dim, dim, kernel_size, groups=groups, padding=kernel_size // 2), + nn.Mish(), + nn.Conv1d(dim, dim, kernel_size, groups=groups, padding=kernel_size // 2), + nn.Mish(), + ) + + def forward(self, x: float["b n d"], mask: bool["b n"] | None = None): + if mask is not None: + mask = mask[..., None] + x = x.masked_fill(~mask, 0.0) + + x = x.permute(0, 2, 1) + x = self.conv1d(x) + out = x.permute(0, 2, 1) + + if mask is not None: + out = out.masked_fill(~mask, 0.0) + + return out + + +# rotary positional embedding related + + +def precompute_freqs_cis(dim: int, end: int, theta: float = 10000.0, theta_rescale_factor=1.0): + # proposed by reddit user bloc97, to rescale rotary embeddings to longer sequence length without fine-tuning + # has some connection to NTK literature + # https://www.reddit.com/r/LocalLLaMA/comments/14lz7j5/ntkaware_scaled_rope_allows_llama_models_to_have/ + # https://github.com/lucidrains/rotary-embedding-torch/blob/main/rotary_embedding_torch/rotary_embedding_torch.py + theta *= theta_rescale_factor ** (dim / (dim - 2)) + freqs = 1.0 / (theta ** (torch.arange(0, dim, 2)[: (dim // 2)].float() / dim)) + t = torch.arange(end, device=freqs.device) # type: ignore + freqs = torch.outer(t, freqs).float() # type: ignore + freqs_cos = torch.cos(freqs) # real part + freqs_sin = torch.sin(freqs) # imaginary part + return torch.cat([freqs_cos, freqs_sin], dim=-1) + + +def get_pos_embed_indices(start, length, max_pos, scale=1.0): + # length = length if isinstance(length, int) else length.max() + scale = scale * torch.ones_like(start, dtype=torch.float32) # in case scale is a scalar + pos = ( + start.unsqueeze(1) + + (torch.arange(length, device=start.device, dtype=torch.float32).unsqueeze(0) * scale.unsqueeze(1)).long() + ) + # avoid extra long error. + pos = torch.where(pos < max_pos, pos, max_pos - 1) + return pos + + +# Global Response Normalization layer (Instance Normalization ?) + + +class GRN(nn.Module): + def __init__(self, dim): + super().__init__() + self.gamma = nn.Parameter(torch.zeros(1, 1, dim)) + self.beta = nn.Parameter(torch.zeros(1, 1, dim)) + + def forward(self, x): + Gx = torch.norm(x, p=2, dim=1, keepdim=True) + Nx = Gx / (Gx.mean(dim=-1, keepdim=True) + 1e-6) + return self.gamma * (x * Nx) + self.beta + x + + +# ConvNeXt-V2 Block https://github.com/facebookresearch/ConvNeXt-V2/blob/main/models/convnextv2.py +# ref: https://github.com/bfs18/e2_tts/blob/main/rfwave/modules.py#L108 + + +class ConvNeXtV2Block(nn.Module): + def __init__( + self, + dim: int, + intermediate_dim: int, + dilation: int = 1, + ): + super().__init__() + padding = (dilation * (7 - 1)) // 2 + self.dwconv = nn.Conv1d( + dim, dim, kernel_size=7, padding=padding, groups=dim, dilation=dilation + ) # depthwise conv + self.norm = nn.LayerNorm(dim, eps=1e-6) + self.pwconv1 = nn.Linear(dim, intermediate_dim) # pointwise/1x1 convs, implemented with linear layers + self.act = nn.GELU() + self.grn = GRN(intermediate_dim) + self.pwconv2 = nn.Linear(intermediate_dim, dim) + + def forward(self, x: torch.Tensor) -> torch.Tensor: + residual = x + x = x.transpose(1, 2) # b n d -> b d n + x = self.dwconv(x) + x = x.transpose(1, 2) # b d n -> b n d + x = self.norm(x) + x = self.pwconv1(x) + x = self.act(x) + x = self.grn(x) + x = self.pwconv2(x) + return residual + x + + +# RMSNorm + + +class RMSNorm(nn.Module): + def __init__(self, dim: int, eps: float): + super().__init__() + self.eps = eps + self.weight = nn.Parameter(torch.ones(dim)) + self.native_rms_norm = float(torch.__version__[:3]) >= 2.4 + + def forward(self, x): + if self.native_rms_norm: + if self.weight.dtype in [torch.float16, torch.bfloat16]: + x = x.to(self.weight.dtype) + x = F.rms_norm(x, normalized_shape=(x.shape[-1],), weight=self.weight, eps=self.eps) + else: + variance = x.to(torch.float32).pow(2).mean(-1, keepdim=True) + x = x * torch.rsqrt(variance + self.eps) + if self.weight.dtype in [torch.float16, torch.bfloat16]: + x = x.to(self.weight.dtype) + x = x * self.weight + + return x + + +# AdaLayerNorm +# return with modulated x for attn input, and params for later mlp modulation + + +class AdaLayerNorm(nn.Module): + def __init__(self, dim): + super().__init__() + + self.silu = nn.SiLU() + self.linear = nn.Linear(dim, dim * 6) + + self.norm = nn.LayerNorm(dim, elementwise_affine=False, eps=1e-6) + + def forward(self, x, emb=None): + emb = self.linear(self.silu(emb)) + shift_msa, scale_msa, gate_msa, shift_mlp, scale_mlp, gate_mlp = torch.chunk(emb, 6, dim=1) + + x = self.norm(x) * (1 + scale_msa[:, None]) + shift_msa[:, None] + return x, gate_msa, shift_mlp, scale_mlp, gate_mlp + + +# AdaLayerNorm for final layer +# return only with modulated x for attn input, cuz no more mlp modulation + + +class AdaLayerNorm_Final(nn.Module): + def __init__(self, dim): + super().__init__() + + self.silu = nn.SiLU() + self.linear = nn.Linear(dim, dim * 2) + + self.norm = nn.LayerNorm(dim, elementwise_affine=False, eps=1e-6) + + def forward(self, x, emb): + emb = self.linear(self.silu(emb)) + scale, shift = torch.chunk(emb, 2, dim=1) + + x = self.norm(x) * (1 + scale)[:, None, :] + shift[:, None, :] + return x + + +# FeedForward + + +class FeedForward(nn.Module): + def __init__(self, dim, dim_out=None, mult=4, dropout=0.0, approximate: str = "none"): + super().__init__() + inner_dim = int(dim * mult) + dim_out = dim_out if dim_out is not None else dim + + activation = nn.GELU(approximate=approximate) + project_in = nn.Sequential(nn.Linear(dim, inner_dim), activation) + self.ff = nn.Sequential(project_in, nn.Dropout(dropout), nn.Linear(inner_dim, dim_out)) + + def forward(self, x): + return self.ff(x) + + +# Attention with possible joint part +# modified from diffusers/src/diffusers/models/attention_processor.py + + +class Attention(nn.Module): + def __init__( + self, + processor: JointAttnProcessor | AttnProcessor, + dim: int, + heads: int = 8, + dim_head: int = 64, + dropout: float = 0.0, + context_dim: Optional[int] = None, # if not None -> joint attention + context_pre_only: bool = False, + qk_norm: Optional[str] = None, + ): + super().__init__() + + if not hasattr(F, "scaled_dot_product_attention"): + raise ImportError("Attention equires PyTorch 2.0, to use it, please upgrade PyTorch to 2.0.") + + self.processor = processor + + self.dim = dim + self.heads = heads + self.inner_dim = dim_head * heads + self.dropout = dropout + + self.context_dim = context_dim + self.context_pre_only = context_pre_only + + self.to_q = nn.Linear(dim, self.inner_dim) + self.to_k = nn.Linear(dim, self.inner_dim) + self.to_v = nn.Linear(dim, self.inner_dim) + + if qk_norm is None: + self.q_norm = None + self.k_norm = None + elif qk_norm == "rms_norm": + self.q_norm = RMSNorm(dim_head, eps=1e-6) + self.k_norm = RMSNorm(dim_head, eps=1e-6) + else: + raise ValueError(f"Unimplemented qk_norm: {qk_norm}") + + if self.context_dim is not None: + self.to_q_c = nn.Linear(context_dim, self.inner_dim) + self.to_k_c = nn.Linear(context_dim, self.inner_dim) + self.to_v_c = nn.Linear(context_dim, self.inner_dim) + if qk_norm is None: + self.c_q_norm = None + self.c_k_norm = None + elif qk_norm == "rms_norm": + self.c_q_norm = RMSNorm(dim_head, eps=1e-6) + self.c_k_norm = RMSNorm(dim_head, eps=1e-6) + + self.to_out = nn.ModuleList([]) + self.to_out.append(nn.Linear(self.inner_dim, dim)) + self.to_out.append(nn.Dropout(dropout)) + + if self.context_dim is not None and not self.context_pre_only: + self.to_out_c = nn.Linear(self.inner_dim, context_dim) + + def forward( + self, + x: float["b n d"], # noised input x + c: float["b n d"] = None, # context c + mask: bool["b n"] | None = None, + rope=None, # rotary position embedding for x + c_rope=None, # rotary position embedding for c + ) -> torch.Tensor: + if c is not None: + return self.processor(self, x, c=c, mask=mask, rope=rope, c_rope=c_rope) + else: + return self.processor(self, x, mask=mask, rope=rope) + + +# Attention processor + +if is_package_available("flash_attn"): + from flash_attn.bert_padding import pad_input, unpad_input + from flash_attn import flash_attn_varlen_func, flash_attn_func + + +class AttnProcessor: + def __init__( + self, + pe_attn_head: int | None = None, # number of attention head to apply rope, None for all + attn_backend: str = "torch", # "torch" or "flash_attn" + attn_mask_enabled: bool = True, + ): + if attn_backend == "flash_attn": + assert is_package_available("flash_attn"), "Please install flash-attn first." + + self.pe_attn_head = pe_attn_head + self.attn_backend = attn_backend + self.attn_mask_enabled = attn_mask_enabled + + def __call__( + self, + attn: Attention, + x: float["b n d"], # noised input x + mask: bool["b n"] | None = None, + rope=None, # rotary position embedding + ) -> torch.FloatTensor: + batch_size = x.shape[0] + + # `sample` projections + query = attn.to_q(x) + key = attn.to_k(x) + value = attn.to_v(x) + + # attention + inner_dim = key.shape[-1] + head_dim = inner_dim // attn.heads + query = query.view(batch_size, -1, attn.heads, head_dim).transpose(1, 2) + key = key.view(batch_size, -1, attn.heads, head_dim).transpose(1, 2) + value = value.view(batch_size, -1, attn.heads, head_dim).transpose(1, 2) + + # qk norm + if attn.q_norm is not None: + query = attn.q_norm(query) + if attn.k_norm is not None: + key = attn.k_norm(key) + + # apply rotary position embedding + if rope is not None: + freqs, xpos_scale = rope + q_xpos_scale, k_xpos_scale = (xpos_scale, xpos_scale**-1.0) if xpos_scale is not None else (1.0, 1.0) + + if self.pe_attn_head is not None: + pn = self.pe_attn_head + query[:, :pn, :, :] = apply_rotary_pos_emb(query[:, :pn, :, :], freqs, q_xpos_scale) + key[:, :pn, :, :] = apply_rotary_pos_emb(key[:, :pn, :, :], freqs, k_xpos_scale) + else: + query = apply_rotary_pos_emb(query, freqs, q_xpos_scale) + key = apply_rotary_pos_emb(key, freqs, k_xpos_scale) + + if self.attn_backend == "torch": + # mask. e.g. inference got a batch with different target durations, mask out the padding + if self.attn_mask_enabled and mask is not None: + attn_mask = mask + attn_mask = attn_mask.unsqueeze(1).unsqueeze(1) # 'b n -> b 1 1 n' + attn_mask = attn_mask.expand(batch_size, attn.heads, query.shape[-2], key.shape[-2]) + else: + attn_mask = None + x = F.scaled_dot_product_attention(query, key, value, attn_mask=attn_mask, dropout_p=0.0, is_causal=False) + x = x.transpose(1, 2).reshape(batch_size, -1, attn.heads * head_dim) + + elif self.attn_backend == "flash_attn": + query = query.transpose(1, 2) # [b, h, n, d] -> [b, n, h, d] + key = key.transpose(1, 2) + value = value.transpose(1, 2) + if self.attn_mask_enabled and mask is not None: + query, indices, q_cu_seqlens, q_max_seqlen_in_batch, _ = unpad_input(query, mask) + key, _, k_cu_seqlens, k_max_seqlen_in_batch, _ = unpad_input(key, mask) + value, _, _, _, _ = unpad_input(value, mask) + x = flash_attn_varlen_func( + query, + key, + value, + q_cu_seqlens, + k_cu_seqlens, + q_max_seqlen_in_batch, + k_max_seqlen_in_batch, + ) + x = pad_input(x, indices, batch_size, q_max_seqlen_in_batch) + x = x.reshape(batch_size, -1, attn.heads * head_dim) + else: + x = flash_attn_func(query, key, value, dropout_p=0.0, causal=False) + x = x.reshape(batch_size, -1, attn.heads * head_dim) + + x = x.to(query.dtype) + + # linear proj + x = attn.to_out[0](x) + # dropout + x = attn.to_out[1](x) + + if mask is not None: + mask = mask.unsqueeze(-1) + x = x.masked_fill(~mask, 0.0) + + return x + + +# Joint Attention processor for MM-DiT +# modified from diffusers/src/diffusers/models/attention_processor.py + + +class JointAttnProcessor: + def __init__(self): + pass + + def __call__( + self, + attn: Attention, + x: float["b n d"], # noised input x + c: float["b nt d"] = None, # context c, here text + mask: bool["b n"] | None = None, + rope=None, # rotary position embedding for x + c_rope=None, # rotary position embedding for c + ) -> torch.FloatTensor: + residual = x + + batch_size = c.shape[0] + + # `sample` projections + query = attn.to_q(x) + key = attn.to_k(x) + value = attn.to_v(x) + + # `context` projections + c_query = attn.to_q_c(c) + c_key = attn.to_k_c(c) + c_value = attn.to_v_c(c) + + # attention + inner_dim = key.shape[-1] + head_dim = inner_dim // attn.heads + query = query.view(batch_size, -1, attn.heads, head_dim).transpose(1, 2) + key = key.view(batch_size, -1, attn.heads, head_dim).transpose(1, 2) + value = value.view(batch_size, -1, attn.heads, head_dim).transpose(1, 2) + c_query = c_query.view(batch_size, -1, attn.heads, head_dim).transpose(1, 2) + c_key = c_key.view(batch_size, -1, attn.heads, head_dim).transpose(1, 2) + c_value = c_value.view(batch_size, -1, attn.heads, head_dim).transpose(1, 2) + + # qk norm + if attn.q_norm is not None: + query = attn.q_norm(query) + if attn.k_norm is not None: + key = attn.k_norm(key) + if attn.c_q_norm is not None: + c_query = attn.c_q_norm(c_query) + if attn.c_k_norm is not None: + c_key = attn.c_k_norm(c_key) + + # apply rope for context and noised input independently + if rope is not None: + freqs, xpos_scale = rope + q_xpos_scale, k_xpos_scale = (xpos_scale, xpos_scale**-1.0) if xpos_scale is not None else (1.0, 1.0) + query = apply_rotary_pos_emb(query, freqs, q_xpos_scale) + key = apply_rotary_pos_emb(key, freqs, k_xpos_scale) + if c_rope is not None: + freqs, xpos_scale = c_rope + q_xpos_scale, k_xpos_scale = (xpos_scale, xpos_scale**-1.0) if xpos_scale is not None else (1.0, 1.0) + c_query = apply_rotary_pos_emb(c_query, freqs, q_xpos_scale) + c_key = apply_rotary_pos_emb(c_key, freqs, k_xpos_scale) + + # joint attention + query = torch.cat([query, c_query], dim=2) + key = torch.cat([key, c_key], dim=2) + value = torch.cat([value, c_value], dim=2) + + # mask. e.g. inference got a batch with different target durations, mask out the padding + if mask is not None: + attn_mask = F.pad(mask, (0, c.shape[1]), value=True) # no mask for c (text) + attn_mask = attn_mask.unsqueeze(1).unsqueeze(1) # 'b n -> b 1 1 n' + attn_mask = attn_mask.expand(batch_size, attn.heads, query.shape[-2], key.shape[-2]) + else: + attn_mask = None + + x = F.scaled_dot_product_attention(query, key, value, attn_mask=attn_mask, dropout_p=0.0, is_causal=False) + x = x.transpose(1, 2).reshape(batch_size, -1, attn.heads * head_dim) + x = x.to(query.dtype) + + # Split the attention outputs. + x, c = ( + x[:, : residual.shape[1]], + x[:, residual.shape[1] :], + ) + + # linear proj + x = attn.to_out[0](x) + # dropout + x = attn.to_out[1](x) + if not attn.context_pre_only: + c = attn.to_out_c(c) + + if mask is not None: + mask = mask.unsqueeze(-1) + x = x.masked_fill(~mask, 0.0) + # c = c.masked_fill(~mask, 0.) # no mask for c (text) + + return x, c + + +# DiT Block + + +class DiTBlock(nn.Module): + def __init__( + self, + dim, + heads, + dim_head, + ff_mult=4, + dropout=0.1, + qk_norm=None, + pe_attn_head=None, + attn_backend="torch", # "torch" or "flash_attn" + attn_mask_enabled=True, + ): + super().__init__() + + self.attn_norm = AdaLayerNorm(dim) + self.attn = Attention( + processor=AttnProcessor( + pe_attn_head=pe_attn_head, + attn_backend=attn_backend, + attn_mask_enabled=attn_mask_enabled, + ), + dim=dim, + heads=heads, + dim_head=dim_head, + dropout=dropout, + qk_norm=qk_norm, + ) + + self.ff_norm = nn.LayerNorm(dim, elementwise_affine=False, eps=1e-6) + self.ff = FeedForward(dim=dim, mult=ff_mult, dropout=dropout, approximate="tanh") + + def forward(self, x, t, mask=None, rope=None): # x: noised input, t: time embedding + # pre-norm & modulation for attention input + norm, gate_msa, shift_mlp, scale_mlp, gate_mlp = self.attn_norm(x, emb=t) + + # attention + attn_output = self.attn(x=norm, mask=mask, rope=rope) + + # process attention output for input x + x = x + gate_msa.unsqueeze(1) * attn_output + + norm = self.ff_norm(x) * (1 + scale_mlp[:, None]) + shift_mlp[:, None] + ff_output = self.ff(norm) + x = x + gate_mlp.unsqueeze(1) * ff_output + + return x + + +# MMDiT Block https://arxiv.org/abs/2403.03206 + + +class MMDiTBlock(nn.Module): + r""" + modified from diffusers/src/diffusers/models/attention.py + + notes. + _c: context related. text, cond, etc. (left part in sd3 fig2.b) + _x: noised input related. (right part) + context_pre_only: last layer only do prenorm + modulation cuz no more ffn + """ + + def __init__( + self, dim, heads, dim_head, ff_mult=4, dropout=0.1, context_dim=None, context_pre_only=False, qk_norm=None + ): + super().__init__() + if context_dim is None: + context_dim = dim + self.context_pre_only = context_pre_only + + self.attn_norm_c = AdaLayerNorm_Final(context_dim) if context_pre_only else AdaLayerNorm(context_dim) + self.attn_norm_x = AdaLayerNorm(dim) + self.attn = Attention( + processor=JointAttnProcessor(), + dim=dim, + heads=heads, + dim_head=dim_head, + dropout=dropout, + context_dim=context_dim, + context_pre_only=context_pre_only, + qk_norm=qk_norm, + ) + + if not context_pre_only: + self.ff_norm_c = nn.LayerNorm(context_dim, elementwise_affine=False, eps=1e-6) + self.ff_c = FeedForward(dim=context_dim, mult=ff_mult, dropout=dropout, approximate="tanh") + else: + self.ff_norm_c = None + self.ff_c = None + self.ff_norm_x = nn.LayerNorm(dim, elementwise_affine=False, eps=1e-6) + self.ff_x = FeedForward(dim=dim, mult=ff_mult, dropout=dropout, approximate="tanh") + + def forward(self, x, c, t, mask=None, rope=None, c_rope=None): # x: noised input, c: context, t: time embedding + # pre-norm & modulation for attention input + if self.context_pre_only: + norm_c = self.attn_norm_c(c, t) + else: + norm_c, c_gate_msa, c_shift_mlp, c_scale_mlp, c_gate_mlp = self.attn_norm_c(c, emb=t) + norm_x, x_gate_msa, x_shift_mlp, x_scale_mlp, x_gate_mlp = self.attn_norm_x(x, emb=t) + + # attention + x_attn_output, c_attn_output = self.attn(x=norm_x, c=norm_c, mask=mask, rope=rope, c_rope=c_rope) + + # process attention output for context c + if self.context_pre_only: + c = None + else: # if not last layer + c = c + c_gate_msa.unsqueeze(1) * c_attn_output + + norm_c = self.ff_norm_c(c) * (1 + c_scale_mlp[:, None]) + c_shift_mlp[:, None] + c_ff_output = self.ff_c(norm_c) + c = c + c_gate_mlp.unsqueeze(1) * c_ff_output + + # process attention output for input x + x = x + x_gate_msa.unsqueeze(1) * x_attn_output + + norm_x = self.ff_norm_x(x) * (1 + x_scale_mlp[:, None]) + x_shift_mlp[:, None] + x_ff_output = self.ff_x(norm_x) + x = x + x_gate_mlp.unsqueeze(1) * x_ff_output + + return c, x + + +# time step conditioning embedding + + +class TimestepEmbedding(nn.Module): + def __init__(self, dim, freq_embed_dim=256): + super().__init__() + self.time_embed = SinusPositionEmbedding(freq_embed_dim) + self.time_mlp = nn.Sequential(nn.Linear(freq_embed_dim, dim), nn.SiLU(), nn.Linear(dim, dim)) + + def forward(self, timestep: float["b"]): + time_hidden = self.time_embed(timestep) + time_hidden = time_hidden.to(timestep.dtype) + time = self.time_mlp(time_hidden) # b d + return time diff --git a/src/f5_tts/src/f5_tts/model/trainer.py b/src/f5_tts/src/f5_tts/model/trainer.py new file mode 100644 index 0000000000000000000000000000000000000000..df6182d385c54dfecb2443c7b063d9ff33c56f2d --- /dev/null +++ b/src/f5_tts/src/f5_tts/model/trainer.py @@ -0,0 +1,439 @@ +from __future__ import annotations + +import gc +import math +import os + +import torch +import torchaudio +import wandb +from accelerate import Accelerator +from accelerate.utils import DistributedDataParallelKwargs +from ema_pytorch import EMA +from torch.optim import AdamW +from torch.optim.lr_scheduler import LinearLR, SequentialLR +from torch.utils.data import DataLoader, Dataset, SequentialSampler +from tqdm import tqdm + +from f5_tts.model import CFM +from f5_tts.model.dataset import DynamicBatchSampler, collate_fn +from f5_tts.model.utils import default, exists + + +# trainer + + +class Trainer: + def __init__( + self, + model: CFM, + epochs, + learning_rate, + num_warmup_updates=20000, + save_per_updates=1000, + keep_last_n_checkpoints: int = -1, # -1 to keep all, 0 to not save intermediate, > 0 to keep last N checkpoints + checkpoint_path=None, + batch_size_per_gpu=32, + batch_size_type: str = "sample", + max_samples=32, + grad_accumulation_steps=1, + max_grad_norm=1.0, + noise_scheduler: str | None = None, + duration_predictor: torch.nn.Module | None = None, + logger: str | None = "wandb", # "wandb" | "tensorboard" | None + wandb_project="test_f5-tts", + wandb_run_name="test_run", + wandb_resume_id: str = None, + log_samples: bool = False, + last_per_updates=None, + accelerate_kwargs: dict = dict(), + ema_kwargs: dict = dict(), + bnb_optimizer: bool = False, + mel_spec_type: str = "vocos", # "vocos" | "bigvgan" + is_local_vocoder: bool = False, # use local path vocoder + local_vocoder_path: str = "", # local vocoder path + model_cfg_dict: dict = dict(), # training config + ): + ddp_kwargs = DistributedDataParallelKwargs(find_unused_parameters=True) + + if logger == "wandb" and not wandb.api.api_key: + logger = None + self.log_samples = log_samples + + self.accelerator = Accelerator( + log_with=logger if logger == "wandb" else None, + kwargs_handlers=[ddp_kwargs], + gradient_accumulation_steps=grad_accumulation_steps, + **accelerate_kwargs, + ) + + self.logger = logger + if self.logger == "wandb": + if exists(wandb_resume_id): + init_kwargs = {"wandb": {"resume": "allow", "name": wandb_run_name, "id": wandb_resume_id}} + else: + init_kwargs = {"wandb": {"resume": "allow", "name": wandb_run_name}} + + if not model_cfg_dict: + model_cfg_dict = { + "epochs": epochs, + "learning_rate": learning_rate, + "num_warmup_updates": num_warmup_updates, + "batch_size_per_gpu": batch_size_per_gpu, + "batch_size_type": batch_size_type, + "max_samples": max_samples, + "grad_accumulation_steps": grad_accumulation_steps, + "max_grad_norm": max_grad_norm, + "noise_scheduler": noise_scheduler, + } + model_cfg_dict["gpus"] = self.accelerator.num_processes + self.accelerator.init_trackers( + project_name=wandb_project, + init_kwargs=init_kwargs, + config=model_cfg_dict, + ) + + elif self.logger == "tensorboard": + from torch.utils.tensorboard import SummaryWriter + + self.writer = SummaryWriter(log_dir=f"runs/{wandb_run_name}") + + self.model = model + + if self.is_main: + self.ema_model = EMA(model, include_online_model=False, **ema_kwargs) + self.ema_model.to(self.accelerator.device) + + print(f"Using logger: {logger}") + if grad_accumulation_steps > 1: + print( + "Gradient accumulation checkpointing with per_updates now, old logic per_steps used with before f992c4e" + ) + + self.epochs = epochs + self.num_warmup_updates = num_warmup_updates + self.save_per_updates = save_per_updates + self.keep_last_n_checkpoints = keep_last_n_checkpoints + self.last_per_updates = default(last_per_updates, save_per_updates) + self.checkpoint_path = default(checkpoint_path, "ckpts/test_f5-tts") + + self.batch_size_per_gpu = batch_size_per_gpu + self.batch_size_type = batch_size_type + self.max_samples = max_samples + self.grad_accumulation_steps = grad_accumulation_steps + self.max_grad_norm = max_grad_norm + + # mel vocoder config + self.vocoder_name = mel_spec_type + self.is_local_vocoder = is_local_vocoder + self.local_vocoder_path = local_vocoder_path + + self.noise_scheduler = noise_scheduler + + self.duration_predictor = duration_predictor + + if bnb_optimizer: + import bitsandbytes as bnb + + self.optimizer = bnb.optim.AdamW8bit(model.parameters(), lr=learning_rate) + else: + self.optimizer = AdamW(model.parameters(), lr=learning_rate) + self.model, self.optimizer = self.accelerator.prepare(self.model, self.optimizer) + + @property + def is_main(self): + return self.accelerator.is_main_process + + def save_checkpoint(self, update, last=False): + self.accelerator.wait_for_everyone() + if self.is_main: + checkpoint = dict( + model_state_dict=self.accelerator.unwrap_model(self.model).state_dict(), + optimizer_state_dict=self.optimizer.state_dict(), + ema_model_state_dict=self.ema_model.state_dict(), + scheduler_state_dict=self.scheduler.state_dict(), + update=update, + ) + if not os.path.exists(self.checkpoint_path): + os.makedirs(self.checkpoint_path) + if last: + self.accelerator.save(checkpoint, f"{self.checkpoint_path}/model_last.pt") + print(f"Saved last checkpoint at update {update}") + else: + if self.keep_last_n_checkpoints == 0: + return + self.accelerator.save(checkpoint, f"{self.checkpoint_path}/model_{update}.pt") + if self.keep_last_n_checkpoints > 0: + # Updated logic to exclude pretrained model from rotation + checkpoints = [ + f + for f in os.listdir(self.checkpoint_path) + if f.startswith("model_") + and not f.startswith("pretrained_") # Exclude pretrained models + and f.endswith(".pt") + and f != "model_last.pt" + ] + checkpoints.sort(key=lambda x: int(x.split("_")[1].split(".")[0])) + while len(checkpoints) > self.keep_last_n_checkpoints: + oldest_checkpoint = checkpoints.pop(0) + os.remove(os.path.join(self.checkpoint_path, oldest_checkpoint)) + print(f"Removed old checkpoint: {oldest_checkpoint}") + + def load_checkpoint(self): + if ( + not exists(self.checkpoint_path) + or not os.path.exists(self.checkpoint_path) + or not any(filename.endswith((".pt", ".safetensors")) for filename in os.listdir(self.checkpoint_path)) + ): + return 0 + + self.accelerator.wait_for_everyone() + if "model_last.pt" in os.listdir(self.checkpoint_path): + latest_checkpoint = "model_last.pt" + else: + # Updated to consider pretrained models for loading but prioritize training checkpoints + all_checkpoints = [ + f + for f in os.listdir(self.checkpoint_path) + if (f.startswith("model_") or f.startswith("pretrained_")) and f.endswith((".pt", ".safetensors")) + ] + + # First try to find regular training checkpoints + training_checkpoints = [f for f in all_checkpoints if f.startswith("model_") and f != "model_last.pt"] + if training_checkpoints: + latest_checkpoint = sorted( + training_checkpoints, + key=lambda x: int("".join(filter(str.isdigit, x))), + )[-1] + else: + # If no training checkpoints, use pretrained model + latest_checkpoint = next(f for f in all_checkpoints if f.startswith("pretrained_")) + + if latest_checkpoint.endswith(".safetensors"): # always a pretrained checkpoint + from safetensors.torch import load_file + + checkpoint = load_file(f"{self.checkpoint_path}/{latest_checkpoint}", device="cpu") + checkpoint = {"ema_model_state_dict": checkpoint} + elif latest_checkpoint.endswith(".pt"): + # checkpoint = torch.load(f"{self.checkpoint_path}/{latest_checkpoint}", map_location=self.accelerator.device) # rather use accelerator.load_state ಥ_ಥ + checkpoint = torch.load( + f"{self.checkpoint_path}/{latest_checkpoint}", weights_only=True, map_location="cpu" + ) + + # patch for backward compatibility, 305e3ea + for key in ["ema_model.mel_spec.mel_stft.mel_scale.fb", "ema_model.mel_spec.mel_stft.spectrogram.window"]: + if key in checkpoint["ema_model_state_dict"]: + del checkpoint["ema_model_state_dict"][key] + + if self.is_main: + self.ema_model.load_state_dict(checkpoint["ema_model_state_dict"]) + + if "update" in checkpoint or "step" in checkpoint: + # patch for backward compatibility, with before f992c4e + if "step" in checkpoint: + checkpoint["update"] = checkpoint["step"] // self.grad_accumulation_steps + if self.grad_accumulation_steps > 1 and self.is_main: + print( + "F5-TTS WARNING: Loading checkpoint saved with per_steps logic (before f992c4e), will convert to per_updates according to grad_accumulation_steps setting, may have unexpected behaviour." + ) + # patch for backward compatibility, 305e3ea + for key in ["mel_spec.mel_stft.mel_scale.fb", "mel_spec.mel_stft.spectrogram.window"]: + if key in checkpoint["model_state_dict"]: + del checkpoint["model_state_dict"][key] + + self.accelerator.unwrap_model(self.model).load_state_dict(checkpoint["model_state_dict"]) + self.optimizer.load_state_dict(checkpoint["optimizer_state_dict"]) + if self.scheduler: + self.scheduler.load_state_dict(checkpoint["scheduler_state_dict"]) + update = checkpoint["update"] + else: + checkpoint["model_state_dict"] = { + k.replace("ema_model.", ""): v + for k, v in checkpoint["ema_model_state_dict"].items() + if k not in ["initted", "update", "step"] + } + self.accelerator.unwrap_model(self.model).load_state_dict(checkpoint["model_state_dict"]) + update = 0 + + del checkpoint + gc.collect() + return update + + def train(self, train_dataset: Dataset, num_workers=16, resumable_with_seed: int = None): + if self.log_samples: + from f5_tts.infer.utils_infer import cfg_strength, load_vocoder, nfe_step, sway_sampling_coef + + vocoder = load_vocoder( + vocoder_name=self.vocoder_name, is_local=self.is_local_vocoder, local_path=self.local_vocoder_path + ) + target_sample_rate = self.accelerator.unwrap_model(self.model).mel_spec.target_sample_rate + log_samples_path = f"{self.checkpoint_path}/samples" + os.makedirs(log_samples_path, exist_ok=True) + + if exists(resumable_with_seed): + generator = torch.Generator() + generator.manual_seed(resumable_with_seed) + else: + generator = None + + if self.batch_size_type == "sample": + train_dataloader = DataLoader( + train_dataset, + collate_fn=collate_fn, + num_workers=num_workers, + pin_memory=True, + persistent_workers=True, + batch_size=self.batch_size_per_gpu, + shuffle=True, + generator=generator, + ) + elif self.batch_size_type == "frame": + self.accelerator.even_batches = False + sampler = SequentialSampler(train_dataset) + batch_sampler = DynamicBatchSampler( + sampler, + self.batch_size_per_gpu, + max_samples=self.max_samples, + random_seed=resumable_with_seed, # This enables reproducible shuffling + drop_residual=False, + ) + train_dataloader = DataLoader( + train_dataset, + collate_fn=collate_fn, + num_workers=num_workers, + pin_memory=True, + persistent_workers=True, + batch_sampler=batch_sampler, + ) + else: + raise ValueError(f"batch_size_type must be either 'sample' or 'frame', but received {self.batch_size_type}") + + # accelerator.prepare() dispatches batches to devices; + # which means the length of dataloader calculated before, should consider the number of devices + warmup_updates = ( + self.num_warmup_updates * self.accelerator.num_processes + ) # consider a fixed warmup steps while using accelerate multi-gpu ddp + # otherwise by default with split_batches=False, warmup steps change with num_processes + total_updates = math.ceil(len(train_dataloader) / self.grad_accumulation_steps) * self.epochs + decay_updates = total_updates - warmup_updates + warmup_scheduler = LinearLR(self.optimizer, start_factor=1e-8, end_factor=1.0, total_iters=warmup_updates) + decay_scheduler = LinearLR(self.optimizer, start_factor=1.0, end_factor=1e-8, total_iters=decay_updates) + self.scheduler = SequentialLR( + self.optimizer, schedulers=[warmup_scheduler, decay_scheduler], milestones=[warmup_updates] + ) + train_dataloader, self.scheduler = self.accelerator.prepare( + train_dataloader, self.scheduler + ) # actual multi_gpu updates = single_gpu updates / gpu nums + start_update = self.load_checkpoint() + global_update = start_update + + if exists(resumable_with_seed): + orig_epoch_step = len(train_dataloader) + start_step = start_update * self.grad_accumulation_steps + skipped_epoch = int(start_step // orig_epoch_step) + skipped_batch = start_step % orig_epoch_step + skipped_dataloader = self.accelerator.skip_first_batches(train_dataloader, num_batches=skipped_batch) + else: + skipped_epoch = 0 + + for epoch in range(skipped_epoch, self.epochs): + self.model.train() + if exists(resumable_with_seed) and epoch == skipped_epoch: + progress_bar_initial = math.ceil(skipped_batch / self.grad_accumulation_steps) + current_dataloader = skipped_dataloader + else: + progress_bar_initial = 0 + current_dataloader = train_dataloader + + # Set epoch for the batch sampler if it exists + if hasattr(train_dataloader, "batch_sampler") and hasattr(train_dataloader.batch_sampler, "set_epoch"): + train_dataloader.batch_sampler.set_epoch(epoch) + + progress_bar = tqdm( + range(math.ceil(len(train_dataloader) / self.grad_accumulation_steps)), + desc=f"Epoch {epoch + 1}/{self.epochs}", + unit="update", + disable=not self.accelerator.is_local_main_process, + initial=progress_bar_initial, + ) + + for batch in current_dataloader: + with self.accelerator.accumulate(self.model): + text_inputs = batch["text"] + mel_spec = batch["mel"].permute(0, 2, 1) + mel_lengths = batch["mel_lengths"] + + # TODO. add duration predictor training + if self.duration_predictor is not None and self.accelerator.is_local_main_process: + dur_loss = self.duration_predictor(mel_spec, lens=batch.get("durations")) + self.accelerator.log({"duration loss": dur_loss.item()}, step=global_update) + + loss, cond, pred = self.model( + mel_spec, text=text_inputs, lens=mel_lengths, noise_scheduler=self.noise_scheduler + ) + self.accelerator.backward(loss) + + if self.max_grad_norm > 0 and self.accelerator.sync_gradients: + self.accelerator.clip_grad_norm_(self.model.parameters(), self.max_grad_norm) + + self.optimizer.step() + self.scheduler.step() + self.optimizer.zero_grad() + + if self.accelerator.sync_gradients: + if self.is_main: + self.ema_model.update() + + global_update += 1 + progress_bar.update(1) + progress_bar.set_postfix(update=str(global_update), loss=loss.item()) + + if self.accelerator.is_local_main_process: + self.accelerator.log( + {"loss": loss.item(), "lr": self.scheduler.get_last_lr()[0]}, step=global_update + ) + if self.logger == "tensorboard": + self.writer.add_scalar("loss", loss.item(), global_update) + self.writer.add_scalar("lr", self.scheduler.get_last_lr()[0], global_update) + + if global_update % self.last_per_updates == 0 and self.accelerator.sync_gradients: + self.save_checkpoint(global_update, last=True) + + if global_update % self.save_per_updates == 0 and self.accelerator.sync_gradients: + self.save_checkpoint(global_update) + + if self.log_samples and self.accelerator.is_local_main_process: + ref_audio_len = mel_lengths[0] + infer_text = [ + text_inputs[0] + ([" "] if isinstance(text_inputs[0], list) else " ") + text_inputs[0] + ] + with torch.inference_mode(): + generated, _ = self.accelerator.unwrap_model(self.model).sample( + cond=mel_spec[0][:ref_audio_len].unsqueeze(0), + text=infer_text, + duration=ref_audio_len * 2, + steps=nfe_step, + cfg_strength=cfg_strength, + sway_sampling_coef=sway_sampling_coef, + ) + generated = generated.to(torch.float32) + gen_mel_spec = generated[:, ref_audio_len:, :].permute(0, 2, 1).to(self.accelerator.device) + ref_mel_spec = batch["mel"][0].unsqueeze(0) + if self.vocoder_name == "vocos": + gen_audio = vocoder.decode(gen_mel_spec).cpu() + ref_audio = vocoder.decode(ref_mel_spec).cpu() + elif self.vocoder_name == "bigvgan": + gen_audio = vocoder(gen_mel_spec).squeeze(0).cpu() + ref_audio = vocoder(ref_mel_spec).squeeze(0).cpu() + + torchaudio.save( + f"{log_samples_path}/update_{global_update}_gen.wav", gen_audio, target_sample_rate + ) + torchaudio.save( + f"{log_samples_path}/update_{global_update}_ref.wav", ref_audio, target_sample_rate + ) + self.model.train() + + self.save_checkpoint(global_update, last=True) + + self.accelerator.end_training() diff --git a/src/f5_tts/src/f5_tts/model/utils.py b/src/f5_tts/src/f5_tts/model/utils.py new file mode 100644 index 0000000000000000000000000000000000000000..68e03097be40bc73e7e7141e322225981014adf2 --- /dev/null +++ b/src/f5_tts/src/f5_tts/model/utils.py @@ -0,0 +1,220 @@ +from __future__ import annotations + +import os +import random +from collections import defaultdict +from importlib.resources import files + +import jieba +import torch +from pypinyin import Style, lazy_pinyin +from torch.nn.utils.rnn import pad_sequence + + +# seed everything + + +def seed_everything(seed=0): + random.seed(seed) + os.environ["PYTHONHASHSEED"] = str(seed) + torch.manual_seed(seed) + torch.cuda.manual_seed(seed) + torch.cuda.manual_seed_all(seed) + torch.backends.cudnn.deterministic = True + torch.backends.cudnn.benchmark = False + + +# helpers + + +def exists(v): + return v is not None + + +def default(v, d): + return v if exists(v) else d + + +def is_package_available(package_name: str) -> bool: + try: + import importlib + + package_exists = importlib.util.find_spec(package_name) is not None + return package_exists + except Exception: + return False + + +# tensor helpers + + +def lens_to_mask(t: int["b"], length: int | None = None) -> bool["b n"]: # noqa: F722 F821 + if not exists(length): + length = t.amax() + + seq = torch.arange(length, device=t.device) + return seq[None, :] < t[:, None] + + +def mask_from_start_end_indices(seq_len: int["b"], start: int["b"], end: int["b"]): # noqa: F722 F821 + max_seq_len = seq_len.max().item() + seq = torch.arange(max_seq_len, device=start.device).long() + start_mask = seq[None, :] >= start[:, None] + end_mask = seq[None, :] < end[:, None] + return start_mask & end_mask + + +def mask_from_frac_lengths(seq_len: int["b"], frac_lengths: float["b"]): # noqa: F722 F821 + lengths = (frac_lengths * seq_len).long() + max_start = seq_len - lengths + + rand = torch.rand_like(frac_lengths) + start = (max_start * rand).long().clamp(min=0) + end = start + lengths + + return mask_from_start_end_indices(seq_len, start, end) + + +def maybe_masked_mean(t: float["b n d"], mask: bool["b n"] = None) -> float["b d"]: # noqa: F722 + if not exists(mask): + return t.mean(dim=1) + + t = torch.where(mask[:, :, None], t, torch.tensor(0.0, device=t.device)) + num = t.sum(dim=1) + den = mask.float().sum(dim=1) + + return num / den.clamp(min=1.0) + + +# simple utf-8 tokenizer, since paper went character based +def list_str_to_tensor(text: list[str], padding_value=-1) -> int["b nt"]: # noqa: F722 + list_tensors = [torch.tensor([*bytes(t, "UTF-8")]) for t in text] # ByT5 style + text = pad_sequence(list_tensors, padding_value=padding_value, batch_first=True) + return text + + +# char tokenizer, based on custom dataset's extracted .txt file +def list_str_to_idx( + text: list[str] | list[list[str]], + vocab_char_map: dict[str, int], # {char: idx} + padding_value=-1, +) -> int["b nt"]: # noqa: F722 + list_idx_tensors = [torch.tensor([vocab_char_map.get(c, 0) for c in t]) for t in text] # pinyin or char style + text = pad_sequence(list_idx_tensors, padding_value=padding_value, batch_first=True) + return text + + +# Get tokenizer + + +def get_tokenizer(dataset_name, tokenizer: str = "pinyin"): + """ + tokenizer - "pinyin" do g2p for only chinese characters, need .txt vocab_file + - "char" for char-wise tokenizer, need .txt vocab_file + - "byte" for utf-8 tokenizer + - "custom" if you're directly passing in a path to the vocab.txt you want to use + vocab_size - if use "pinyin", all available pinyin types, common alphabets (also those with accent) and symbols + - if use "char", derived from unfiltered character & symbol counts of custom dataset + - if use "byte", set to 256 (unicode byte range) + """ + if tokenizer in ["pinyin", "char"]: + tokenizer_path = os.path.join(files("f5_tts").joinpath("../../data"), f"{dataset_name}_{tokenizer}/vocab.txt") + with open(tokenizer_path, "r", encoding="utf-8") as f: + vocab_char_map = {} + for i, char in enumerate(f): + vocab_char_map[char[:-1]] = i + vocab_size = len(vocab_char_map) + assert vocab_char_map[" "] == 0, "make sure space is of idx 0 in vocab.txt, cuz 0 is used for unknown char" + + elif tokenizer == "byte": + vocab_char_map = None + vocab_size = 256 + + elif tokenizer == "custom": + with open(dataset_name, "r", encoding="utf-8") as f: + vocab_char_map = {} + for i, char in enumerate(f): + vocab_char_map[char[:-1]] = i + vocab_size = len(vocab_char_map) + + return vocab_char_map, vocab_size + + +# convert char to pinyin + + +def convert_char_to_pinyin(text_list, polyphone=True): + if jieba.dt.initialized is False: + jieba.default_logger.setLevel(50) # CRITICAL + jieba.initialize() + + final_text_list = [] + custom_trans = str.maketrans( + {";": ",", "“": '"', "”": '"', "‘": "'", "’": "'"} + ) # add custom trans here, to address oov + + def is_chinese(c): + return ( + "\u3100" <= c <= "\u9fff" # common chinese characters + ) + + for text in text_list: + char_list = [] + text = text.translate(custom_trans) + for seg in jieba.cut(text): + seg_byte_len = len(bytes(seg, "UTF-8")) + if seg_byte_len == len(seg): # if pure alphabets and symbols + if char_list and seg_byte_len > 1 and char_list[-1] not in " :'\"": + char_list.append(" ") + char_list.extend(seg) + elif polyphone and seg_byte_len == 3 * len(seg): # if pure east asian characters + seg_ = lazy_pinyin(seg, style=Style.TONE3, tone_sandhi=True) + for i, c in enumerate(seg): + if is_chinese(c): + char_list.append(" ") + char_list.append(seg_[i]) + else: # if mixed characters, alphabets and symbols + for c in seg: + if ord(c) < 256: + char_list.extend(c) + elif is_chinese(c): + char_list.append(" ") + char_list.extend(lazy_pinyin(c, style=Style.TONE3, tone_sandhi=True)) + else: + char_list.append(c) + final_text_list.append(char_list) + + return final_text_list + + +# filter func for dirty data with many repetitions + + +def repetition_found(text, length=2, tolerance=10): + pattern_count = defaultdict(int) + for i in range(len(text) - length + 1): + pattern = text[i : i + length] + pattern_count[pattern] += 1 + for pattern, count in pattern_count.items(): + if count > tolerance: + return True + return False + + +# get the empirically pruned step for sampling + + +def get_epss_timesteps(n, device, dtype): + dt = 1 / 32 + predefined_timesteps = { + 5: [0, 2, 4, 8, 16, 32], + 6: [0, 2, 4, 6, 8, 16, 32], + 7: [0, 2, 4, 6, 8, 16, 24, 32], + 10: [0, 2, 4, 6, 8, 12, 16, 20, 24, 28, 32], + 12: [0, 2, 4, 6, 8, 10, 12, 14, 16, 20, 24, 28, 32], + 16: [0, 1, 2, 3, 4, 5, 6, 7, 8, 10, 12, 14, 16, 20, 24, 28, 32], + } + t = predefined_timesteps.get(n, []) + if not t: + return torch.linspace(0, 1, n + 1, device=device, dtype=dtype) + return dt * torch.tensor(t, device=device, dtype=dtype) diff --git a/src/f5_tts/src/f5_tts/runtime/triton_trtllm/Dockerfile.server b/src/f5_tts/src/f5_tts/runtime/triton_trtllm/Dockerfile.server new file mode 100644 index 0000000000000000000000000000000000000000..4002baf5b8b8a34dc6979608c2ae3b2edfd34437 --- /dev/null +++ b/src/f5_tts/src/f5_tts/runtime/triton_trtllm/Dockerfile.server @@ -0,0 +1,3 @@ +FROM nvcr.io/nvidia/tritonserver:24.12-py3 +RUN pip install tritonclient[grpc] tensorrt-llm==0.16.0 torchaudio==2.5.1 jieba pypinyin librosa vocos +WORKDIR /workspace \ No newline at end of file diff --git a/src/f5_tts/src/f5_tts/runtime/triton_trtllm/README.md b/src/f5_tts/src/f5_tts/runtime/triton_trtllm/README.md new file mode 100644 index 0000000000000000000000000000000000000000..93ed07707ab173f5471104b092526ae4f59c7d24 --- /dev/null +++ b/src/f5_tts/src/f5_tts/runtime/triton_trtllm/README.md @@ -0,0 +1,69 @@ +## Triton Inference Serving Best Practice for F5-TTS + +### Quick Start +Directly launch the service using docker compose. +```sh +# TODO: support F5TTS_v1_Base +MODEL=F5TTS_Base docker compose up +``` + +### Build Image +Build the docker image from scratch. +```sh +docker build . -f Dockerfile.server -t soar97/triton-f5-tts:24.12 +``` + +### Create Docker Container +```sh +your_mount_dir=/mnt:/mnt +docker run -it --name "f5-server" --gpus all --net host -v $your_mount_dir --shm-size=2g soar97/triton-f5-tts:24.12 +``` + +### Export Models to TensorRT-LLM and Launch Server +Inside docker container, we would follow the official guide of TensorRT-LLM to build qwen and whisper TensorRT-LLM engines. See [here](https://github.com/NVIDIA/TensorRT-LLM/tree/main/examples/whisper). +```sh +bash run.sh 0 4 F5TTS_Base +``` + +### HTTP Client +```sh +python3 client_http.py +``` + +### Benchmark using Client-Server Mode +```sh +num_task=2 +python3 client_grpc.py --num-tasks $num_task --huggingface-dataset yuekai/seed_tts --split-name wenetspeech4tts +``` + +### Benchmark using Offline TRT-LLM Mode +```sh +batch_size=1 +split_name=wenetspeech4tts +backend_type=trt +log_dir=./log_benchmark_batch_size_${batch_size}_${split_name}_${backend_type} +rm -r $log_dir +ln -s model_repo_f5_tts/f5_tts/1/f5_tts_trtllm.py ./ +torchrun --nproc_per_node=1 \ +benchmark.py --output-dir $log_dir \ +--batch-size $batch_size \ +--enable-warmup \ +--split-name $split_name \ +--model-path $F5_TTS_HF_DOWNLOAD_PATH/$model/model_1200000.pt \ +--vocab-file $F5_TTS_HF_DOWNLOAD_PATH/$model/vocab.txt \ +--vocoder-trt-engine-path $vocoder_trt_engine_path \ +--backend-type $backend_type \ +--tllm-model-dir $F5_TTS_TRT_LLM_ENGINE_PATH || exit 1 +``` + +### Benchmark Results +Decoding on a single L20 GPU, using 26 different prompt_audio & target_text pairs, 16 NFE. + +| Model | Concurrency | Avg Latency | RTF | Mode | +|---------------------|----------------|-------------|--------|-----------------| +| F5-TTS Base (Vocos) | 2 | 253 ms | 0.0394 | Client-Server | +| F5-TTS Base (Vocos) | 1 (Batch_size) | - | 0.0402 | Offline TRT-LLM | +| F5-TTS Base (Vocos) | 1 (Batch_size) | - | 0.1467 | Offline Pytorch | + +### Credits +1. [F5-TTS-TRTLLM](https://github.com/Bigfishering/f5-tts-trtllm) diff --git a/src/f5_tts/src/f5_tts/runtime/triton_trtllm/benchmark.py b/src/f5_tts/src/f5_tts/runtime/triton_trtllm/benchmark.py new file mode 100644 index 0000000000000000000000000000000000000000..ed55f25c568e4cda01436206e892cbe45b2c7ecf --- /dev/null +++ b/src/f5_tts/src/f5_tts/runtime/triton_trtllm/benchmark.py @@ -0,0 +1,560 @@ +# Copyright (c) 2024 Tsinghua Univ. (authors: Xingchen Song) +# 2025 (authors: Yuekai Zhang) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# Modified from https://github.com/xingchensong/S3Tokenizer/blob/main/s3tokenizer/cli.py +""" Example Usage +torchrun --nproc_per_node=1 \ +benchmark.py --output-dir $log_dir \ +--batch-size $batch_size \ +--enable-warmup \ +--split-name $split_name \ +--model-path $F5_TTS_HF_DOWNLOAD_PATH/$model/model_1200000.pt \ +--vocab-file $F5_TTS_HF_DOWNLOAD_PATH/$model/vocab.txt \ +--vocoder-trt-engine-path $vocoder_trt_engine_path \ +--backend-type $backend_type \ +--tllm-model-dir $F5_TTS_TRT_LLM_ENGINE_PATH || exit 1 +""" + +import argparse +import json +import os +import time +from typing import Dict, List, Union + +import datasets +import jieba +import tensorrt as trt +import torch +import torch.distributed as dist +import torch.nn.functional as F +import torchaudio +from datasets import load_dataset +from f5_tts_trtllm import F5TTS +from huggingface_hub import hf_hub_download +from pypinyin import Style, lazy_pinyin +from tensorrt_llm._utils import trt_dtype_to_torch +from tensorrt_llm.logger import logger +from tensorrt_llm.runtime.session import Session, TensorInfo +from torch.nn.utils.rnn import pad_sequence +from torch.utils.data import DataLoader, DistributedSampler +from tqdm import tqdm +from vocos import Vocos + + +torch.manual_seed(0) + + +def get_args(): + parser = argparse.ArgumentParser(description="extract speech code") + parser.add_argument( + "--split-name", + type=str, + default="wenetspeech4tts", + choices=["wenetspeech4tts", "test_zh", "test_en", "test_hard"], + help="huggingface dataset split name", + ) + parser.add_argument("--output-dir", required=True, type=str, help="dir to save result") + parser.add_argument( + "--vocab-file", + required=True, + type=str, + help="vocab file", + ) + parser.add_argument( + "--model-path", + required=True, + type=str, + help="model path, to load text embedding", + ) + parser.add_argument( + "--tllm-model-dir", + required=True, + type=str, + help="tllm model dir", + ) + parser.add_argument( + "--batch-size", + required=True, + type=int, + help="batch size (per-device) for inference", + ) + parser.add_argument("--num-workers", type=int, default=0, help="workers for dataloader") + parser.add_argument("--prefetch", type=int, default=None, help="prefetch for dataloader") + parser.add_argument( + "--vocoder", + default="vocos", + type=str, + help="vocoder name", + ) + parser.add_argument( + "--vocoder-trt-engine-path", + default=None, + type=str, + help="vocoder trt engine path", + ) + parser.add_argument("--enable-warmup", action="store_true") + parser.add_argument("--remove-input-padding", action="store_true") + parser.add_argument("--use-perf", action="store_true", help="use nvtx to record performance") + parser.add_argument("--backend-type", type=str, default="triton", choices=["trt", "pytorch"], help="backend type") + args = parser.parse_args() + return args + + +def padded_mel_batch(ref_mels, max_seq_len): + padded_ref_mels = [] + for mel in ref_mels: + # pad along the last dimension + padded_ref_mel = F.pad(mel, (0, 0, 0, max_seq_len - mel.shape[0]), value=0) + padded_ref_mels.append(padded_ref_mel) + padded_ref_mels = torch.stack(padded_ref_mels) + return padded_ref_mels + + +def data_collator(batch, vocab_char_map, device="cuda", use_perf=False): + if use_perf: + torch.cuda.nvtx.range_push("data_collator") + target_sample_rate = 24000 + target_rms = 0.1 + ids, ref_mel_list, ref_mel_len_list, estimated_reference_target_mel_len, reference_target_texts_list = ( + [], + [], + [], + [], + [], + ) + for i, item in enumerate(batch): + item_id, prompt_text, target_text = ( + item["id"], + item["prompt_text"], + item["target_text"], + ) + ids.append(item_id) + reference_target_texts_list.append(prompt_text + target_text) + + ref_audio_org, ref_sr = ( + item["prompt_audio"]["array"], + item["prompt_audio"]["sampling_rate"], + ) + ref_audio_org = torch.from_numpy(ref_audio_org).unsqueeze(0).float() + ref_rms = torch.sqrt(torch.mean(torch.square(ref_audio_org))) + if ref_rms < target_rms: + ref_audio_org = ref_audio_org * target_rms / ref_rms + + if ref_sr != target_sample_rate: + resampler = torchaudio.transforms.Resample(ref_sr, target_sample_rate) + ref_audio = resampler(ref_audio_org) + else: + ref_audio = ref_audio_org + + if use_perf: + torch.cuda.nvtx.range_push(f"mel_spectrogram {i}") + ref_mel = mel_spectrogram(ref_audio, vocoder="vocos", device="cuda") + if use_perf: + torch.cuda.nvtx.range_pop() + ref_mel = ref_mel.squeeze() + ref_mel_len = ref_mel.shape[0] + assert ref_mel.shape[1] == 100 + + ref_mel_list.append(ref_mel) + ref_mel_len_list.append(ref_mel_len) + + estimated_reference_target_mel_len.append( + int(ref_mel.shape[0] * (1 + len(target_text.encode("utf-8")) / len(prompt_text.encode("utf-8")))) + ) + + max_seq_len = max(estimated_reference_target_mel_len) + ref_mel_batch = padded_mel_batch(ref_mel_list, max_seq_len) + ref_mel_len_batch = torch.LongTensor(ref_mel_len_list) + + pinyin_list = convert_char_to_pinyin(reference_target_texts_list, polyphone=True) + text_pad_sequence = list_str_to_idx(pinyin_list, vocab_char_map) + + for i, item in enumerate(text_pad_sequence): + text_pad_sequence[i] = F.pad( + item, (0, estimated_reference_target_mel_len[i] - len(item)), mode="constant", value=-1 + ) + text_pad_sequence[i] += 1 # WAR: 0 is reserved for padding token, hard coding in F5-TTS + text_pad_sequence = pad_sequence(text_pad_sequence, padding_value=-1, batch_first=True).to(device) + text_pad_sequence = F.pad( + text_pad_sequence, (0, max_seq_len - text_pad_sequence.shape[1]), mode="constant", value=-1 + ) + if use_perf: + torch.cuda.nvtx.range_pop() + return { + "ids": ids, + "ref_mel_batch": ref_mel_batch, + "ref_mel_len_batch": ref_mel_len_batch, + "text_pad_sequence": text_pad_sequence, + "estimated_reference_target_mel_len": estimated_reference_target_mel_len, + } + + +def init_distributed(): + world_size = int(os.environ.get("WORLD_SIZE", 1)) + local_rank = int(os.environ.get("LOCAL_RANK", 0)) + rank = int(os.environ.get("RANK", 0)) + print( + "Inference on multiple gpus, this gpu {}".format(local_rank) + + ", rank {}, world_size {}".format(rank, world_size) + ) + torch.cuda.set_device(local_rank) + # Initialize process group with explicit device IDs + dist.init_process_group( + "nccl", + ) + return world_size, local_rank, rank + + +def get_tokenizer(vocab_file_path: str): + """ + tokenizer - "pinyin" do g2p for only chinese characters, need .txt vocab_file + - "char" for char-wise tokenizer, need .txt vocab_file + - "byte" for utf-8 tokenizer + - "custom" if you're directly passing in a path to the vocab.txt you want to use + vocab_size - if use "pinyin", all available pinyin types, common alphabets (also those with accent) and symbols + - if use "char", derived from unfiltered character & symbol counts of custom dataset + - if use "byte", set to 256 (unicode byte range) + """ + with open(vocab_file_path, "r", encoding="utf-8") as f: + vocab_char_map = {} + for i, char in enumerate(f): + vocab_char_map[char[:-1]] = i + vocab_size = len(vocab_char_map) + return vocab_char_map, vocab_size + + +def convert_char_to_pinyin(reference_target_texts_list, polyphone=True): + final_reference_target_texts_list = [] + custom_trans = str.maketrans( + {";": ",", "“": '"', "”": '"', "‘": "'", "’": "'"} + ) # add custom trans here, to address oov + + def is_chinese(c): + return "\u3100" <= c <= "\u9fff" # common chinese characters + + for text in reference_target_texts_list: + char_list = [] + text = text.translate(custom_trans) + for seg in jieba.cut(text): + seg_byte_len = len(bytes(seg, "UTF-8")) + if seg_byte_len == len(seg): # if pure alphabets and symbols + if char_list and seg_byte_len > 1 and char_list[-1] not in " :'\"": + char_list.append(" ") + char_list.extend(seg) + elif polyphone and seg_byte_len == 3 * len(seg): # if pure east asian characters + seg_ = lazy_pinyin(seg, style=Style.TONE3, tone_sandhi=True) + for i, c in enumerate(seg): + if is_chinese(c): + char_list.append(" ") + char_list.append(seg_[i]) + else: # if mixed characters, alphabets and symbols + for c in seg: + if ord(c) < 256: + char_list.extend(c) + elif is_chinese(c): + char_list.append(" ") + char_list.extend(lazy_pinyin(c, style=Style.TONE3, tone_sandhi=True)) + else: + char_list.append(c) + final_reference_target_texts_list.append(char_list) + + return final_reference_target_texts_list + + +def list_str_to_idx( + text: Union[List[str], List[List[str]]], + vocab_char_map: Dict[str, int], # {char: idx} + padding_value=-1, +): + list_idx_tensors = [torch.tensor([vocab_char_map.get(c, 0) for c in t]) for t in text] # pinyin or char style + # text = pad_sequence(list_idx_tensors, padding_value=padding_value, batch_first=True) + return list_idx_tensors + + +def load_vocoder( + vocoder_name="vocos", is_local=False, local_path="", device="cuda", hf_cache_dir=None, vocoder_trt_engine_path=None +): + if vocoder_name == "vocos": + if vocoder_trt_engine_path is not None: + vocoder = VocosTensorRT(engine_path=vocoder_trt_engine_path) + else: + # vocoder = Vocos.from_pretrained("charactr/vocos-mel-24khz").to(device) + if is_local: + print(f"Load vocos from local path {local_path}") + config_path = f"{local_path}/config.yaml" + model_path = f"{local_path}/pytorch_model.bin" + else: + print("Download Vocos from huggingface charactr/vocos-mel-24khz") + repo_id = "charactr/vocos-mel-24khz" + config_path = hf_hub_download(repo_id=repo_id, cache_dir=hf_cache_dir, filename="config.yaml") + model_path = hf_hub_download(repo_id=repo_id, cache_dir=hf_cache_dir, filename="pytorch_model.bin") + vocoder = Vocos.from_hparams(config_path) + state_dict = torch.load(model_path, map_location="cpu", weights_only=True) + from vocos.feature_extractors import EncodecFeatures + + if isinstance(vocoder.feature_extractor, EncodecFeatures): + encodec_parameters = { + "feature_extractor.encodec." + key: value + for key, value in vocoder.feature_extractor.encodec.state_dict().items() + } + state_dict.update(encodec_parameters) + vocoder.load_state_dict(state_dict) + vocoder = vocoder.eval().to(device) + elif vocoder_name == "bigvgan": + raise NotImplementedError("BigVGAN is not implemented yet") + return vocoder + + +def mel_spectrogram(waveform, vocoder="vocos", device="cuda"): + if vocoder == "vocos": + mel_stft = torchaudio.transforms.MelSpectrogram( + sample_rate=24000, + n_fft=1024, + win_length=1024, + hop_length=256, + n_mels=100, + power=1, + center=True, + normalized=False, + norm=None, + ).to(device) + mel = mel_stft(waveform.to(device)) + mel = mel.clamp(min=1e-5).log() + return mel.transpose(1, 2) + + +class VocosTensorRT: + def __init__(self, engine_path="./vocos_vocoder.plan", stream=None): + TRT_LOGGER = trt.Logger(trt.Logger.WARNING) + trt.init_libnvinfer_plugins(TRT_LOGGER, namespace="") + logger.info(f"Loading vae engine from {engine_path}") + self.engine_path = engine_path + with open(engine_path, "rb") as f: + engine_buffer = f.read() + self.session = Session.from_serialized_engine(engine_buffer) + self.stream = stream if stream is not None else torch.cuda.current_stream().cuda_stream + + def decode(self, mels): + mels = mels.contiguous() + inputs = {"mel": mels} + output_info = self.session.infer_shapes([TensorInfo("mel", trt.DataType.FLOAT, mels.shape)]) + outputs = { + t.name: torch.empty(tuple(t.shape), dtype=trt_dtype_to_torch(t.dtype), device="cuda") for t in output_info + } + ok = self.session.run(inputs, outputs, self.stream) + + assert ok, "Runtime execution failed for vae session" + + samples = outputs["waveform"] + return samples + + +def main(): + args = get_args() + os.makedirs(args.output_dir, exist_ok=True) + + assert torch.cuda.is_available() + world_size, local_rank, rank = init_distributed() + device = torch.device(f"cuda:{local_rank}") + + vocab_char_map, vocab_size = get_tokenizer(args.vocab_file) + + tllm_model_dir = args.tllm_model_dir + config_file = os.path.join(tllm_model_dir, "config.json") + with open(config_file) as f: + config = json.load(f) + if args.backend_type == "trt": + model = F5TTS( + config, debug_mode=False, tllm_model_dir=tllm_model_dir, model_path=args.model_path, vocab_size=vocab_size + ) + elif args.backend_type == "pytorch": + import sys + + sys.path.append(f"{os.path.dirname(os.path.abspath(__file__))}/../../../../src/") + from f5_tts.infer.utils_infer import load_model + from f5_tts.model import DiT + + F5TTS_model_cfg = dict( + dim=1024, + depth=22, + heads=16, + ff_mult=2, + text_dim=512, + conv_layers=4, + pe_attn_head=1, + text_mask_padding=False, + ) + model = load_model(DiT, F5TTS_model_cfg, args.model_path) + + vocoder = load_vocoder( + vocoder_name=args.vocoder, device=device, vocoder_trt_engine_path=args.vocoder_trt_engine_path + ) + + dataset = load_dataset( + "yuekai/seed_tts", + split=args.split_name, + trust_remote_code=True, + ) + + def add_estimated_duration(example): + prompt_audio_len = example["prompt_audio"]["array"].shape[0] + scale_factor = 1 + len(example["target_text"]) / len(example["prompt_text"]) + estimated_duration = prompt_audio_len * scale_factor + example["estimated_duration"] = estimated_duration / example["prompt_audio"]["sampling_rate"] + return example + + dataset = dataset.map(add_estimated_duration) + dataset = dataset.sort("estimated_duration", reverse=True) + if args.use_perf: + # dataset_list = [dataset.select(range(1)) for i in range(16)] # seq_len 1000 + dataset_list_short = [dataset.select([24]) for i in range(8)] # seq_len 719 + # dataset_list_long = [dataset.select([23]) for i in range(8)] # seq_len 2002 + # dataset = datasets.concatenate_datasets(dataset_list_short + dataset_list_long) + dataset = datasets.concatenate_datasets(dataset_list_short) + if world_size > 1: + sampler = DistributedSampler(dataset, num_replicas=world_size, rank=rank) + else: + # This would disable shuffling + sampler = None + + dataloader = DataLoader( + dataset, + batch_size=args.batch_size, + sampler=sampler, + shuffle=False, + num_workers=args.num_workers, + prefetch_factor=args.prefetch, + collate_fn=lambda x: data_collator(x, vocab_char_map, use_perf=args.use_perf), + ) + + total_steps = len(dataset) + + if args.enable_warmup: + for batch in dataloader: + ref_mels, ref_mel_lens = batch["ref_mel_batch"].to(device), batch["ref_mel_len_batch"].to(device) + text_pad_seq = batch["text_pad_sequence"].to(device) + total_mel_lens = batch["estimated_reference_target_mel_len"] + if args.backend_type == "trt": + _ = model.sample( + text_pad_seq, ref_mels, ref_mel_lens, total_mel_lens, remove_input_padding=args.remove_input_padding + ) + elif args.backend_type == "pytorch": + with torch.inference_mode(): + text_pad_seq -= 1 + text_pad_seq[text_pad_seq == -2] = -1 + total_mel_lens = torch.tensor(total_mel_lens, device=device) + generated, _ = model.sample( + cond=ref_mels, + text=text_pad_seq, + duration=total_mel_lens, + steps=16, + cfg_strength=2.0, + sway_sampling_coef=-1, + ) + + if rank == 0: + progress_bar = tqdm(total=total_steps, desc="Processing", unit="wavs") + + decoding_time = 0 + vocoder_time = 0 + total_duration = 0 + if args.use_perf: + torch.cuda.cudart().cudaProfilerStart() + total_decoding_time = time.time() + for batch in dataloader: + if args.use_perf: + torch.cuda.nvtx.range_push("data sample") + ref_mels, ref_mel_lens = batch["ref_mel_batch"].to(device), batch["ref_mel_len_batch"].to(device) + text_pad_seq = batch["text_pad_sequence"].to(device) + total_mel_lens = batch["estimated_reference_target_mel_len"] + + if args.use_perf: + torch.cuda.nvtx.range_pop() + if args.backend_type == "trt": + generated, cost_time = model.sample( + text_pad_seq, + ref_mels, + ref_mel_lens, + total_mel_lens, + remove_input_padding=args.remove_input_padding, + use_perf=args.use_perf, + ) + elif args.backend_type == "pytorch": + total_mel_lens = torch.tensor(total_mel_lens, device=device) + with torch.inference_mode(): + start_time = time.time() + text_pad_seq -= 1 + text_pad_seq[text_pad_seq == -2] = -1 + generated, _ = model.sample( + cond=ref_mels, + text=text_pad_seq, + duration=total_mel_lens, + lens=ref_mel_lens, + steps=16, + cfg_strength=2.0, + sway_sampling_coef=-1, + ) + cost_time = time.time() - start_time + decoding_time += cost_time + vocoder_start_time = time.time() + for i, gen in enumerate(generated): + gen = gen[ref_mel_lens[i] : total_mel_lens[i], :].unsqueeze(0) + gen_mel_spec = gen.permute(0, 2, 1).to(torch.float32) + if args.vocoder == "vocos": + if args.use_perf: + torch.cuda.nvtx.range_push("vocoder decode") + generated_wave = vocoder.decode(gen_mel_spec).cpu() + if args.use_perf: + torch.cuda.nvtx.range_pop() + else: + generated_wave = vocoder(gen_mel_spec).squeeze(0).cpu() + target_rms = 0.1 + target_sample_rate = 24_000 + # if ref_rms_list[i] < target_rms: + # generated_wave = generated_wave * ref_rms_list[i] / target_rms + rms = torch.sqrt(torch.mean(torch.square(generated_wave))) + if rms < target_rms: + generated_wave = generated_wave * target_rms / rms + utt = batch["ids"][i] + torchaudio.save( + f"{args.output_dir}/{utt}.wav", + generated_wave, + target_sample_rate, + ) + total_duration += generated_wave.shape[1] / target_sample_rate + vocoder_time += time.time() - vocoder_start_time + if rank == 0: + progress_bar.update(world_size * len(batch["ids"])) + total_decoding_time = time.time() - total_decoding_time + if rank == 0: + progress_bar.close() + rtf = total_decoding_time / total_duration + s = f"RTF: {rtf:.4f}\n" + s += f"total_duration: {total_duration:.3f} seconds\n" + s += f"({total_duration / 3600:.2f} hours)\n" + s += f"DiT time: {decoding_time:.3f} seconds ({decoding_time / 3600:.2f} hours)\n" + s += f"Vocoder time: {vocoder_time:.3f} seconds ({vocoder_time / 3600:.2f} hours)\n" + s += f"total decoding time: {total_decoding_time:.3f} seconds ({total_decoding_time / 3600:.2f} hours)\n" + s += f"batch size: {args.batch_size}\n" + print(s) + + with open(f"{args.output_dir}/rtf.txt", "w") as f: + f.write(s) + + dist.barrier() + dist.destroy_process_group() + + +if __name__ == "__main__": + main() diff --git a/src/f5_tts/src/f5_tts/runtime/triton_trtllm/client_grpc.py b/src/f5_tts/src/f5_tts/runtime/triton_trtllm/client_grpc.py new file mode 100644 index 0000000000000000000000000000000000000000..ed6a26ad4c63c95d61b002341fd94cf797ef1b43 --- /dev/null +++ b/src/f5_tts/src/f5_tts/runtime/triton_trtllm/client_grpc.py @@ -0,0 +1,470 @@ +#!/usr/bin/env python3 +# Copyright 2022 Xiaomi Corp. (authors: Fangjun Kuang) +# 2023 Nvidia (authors: Yuekai Zhang) +# 2023 Recurrent.ai (authors: Songtao Shi) +# See LICENSE for clarification regarding multiple authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +""" +This script supports to load dataset from huggingface and sends it to the server +for decoding, in parallel. + +Usage: +num_task=2 + +# For offline F5-TTS +python3 client_grpc.py \ + --server-addr localhost \ + --model-name f5_tts \ + --num-tasks $num_task \ + --huggingface-dataset yuekai/seed_tts \ + --split-name test_zh \ + --log-dir ./log_concurrent_tasks_${num_task} + +# For offline Spark-TTS-0.5B +python3 client_grpc.py \ + --server-addr localhost \ + --model-name spark_tts \ + --num-tasks $num_task \ + --huggingface-dataset yuekai/seed_tts \ + --split-name wenetspeech4tts \ + --log-dir ./log_concurrent_tasks_${num_task} +""" + +import argparse +import asyncio +import json +import os +import time +import types +from pathlib import Path + +import numpy as np +import soundfile as sf +import tritonclient +import tritonclient.grpc.aio as grpcclient +from tritonclient.utils import np_to_triton_dtype + + +def write_triton_stats(stats, summary_file): + with open(summary_file, "w") as summary_f: + model_stats = stats["model_stats"] + # write a note, the log is from triton_client.get_inference_statistics(), to better human readability + summary_f.write( + "The log is parsing from triton_client.get_inference_statistics(), to better human readability. \n" + ) + summary_f.write("To learn more about the log, please refer to: \n") + summary_f.write("1. https://github.com/triton-inference-server/server/blob/main/docs/user_guide/metrics.md \n") + summary_f.write("2. https://github.com/triton-inference-server/server/issues/5374 \n\n") + summary_f.write( + "To better improve throughput, we always would like let requests wait in the queue for a while, and then execute them with a larger batch size. \n" + ) + summary_f.write( + "However, there is a trade-off between the increased queue time and the increased batch size. \n" + ) + summary_f.write( + "You may change 'max_queue_delay_microseconds' and 'preferred_batch_size' in the model configuration file to achieve this. \n" + ) + summary_f.write( + "See https://github.com/triton-inference-server/server/blob/main/docs/user_guide/model_configuration.md#delayed-batching for more details. \n\n" + ) + for model_state in model_stats: + if "last_inference" not in model_state: + continue + summary_f.write(f"model name is {model_state['name']} \n") + model_inference_stats = model_state["inference_stats"] + total_queue_time_s = int(model_inference_stats["queue"]["ns"]) / 1e9 + total_infer_time_s = int(model_inference_stats["compute_infer"]["ns"]) / 1e9 + total_input_time_s = int(model_inference_stats["compute_input"]["ns"]) / 1e9 + total_output_time_s = int(model_inference_stats["compute_output"]["ns"]) / 1e9 + summary_f.write( + f"queue time {total_queue_time_s:<5.2f} s, compute infer time {total_infer_time_s:<5.2f} s, compute input time {total_input_time_s:<5.2f} s, compute output time {total_output_time_s:<5.2f} s \n" # noqa + ) + model_batch_stats = model_state["batch_stats"] + for batch in model_batch_stats: + batch_size = int(batch["batch_size"]) + compute_input = batch["compute_input"] + compute_output = batch["compute_output"] + compute_infer = batch["compute_infer"] + batch_count = int(compute_infer["count"]) + assert compute_infer["count"] == compute_output["count"] == compute_input["count"] + compute_infer_time_ms = int(compute_infer["ns"]) / 1e6 + compute_input_time_ms = int(compute_input["ns"]) / 1e6 + compute_output_time_ms = int(compute_output["ns"]) / 1e6 + summary_f.write( + f"execuate inference with batch_size {batch_size:<2} total {batch_count:<5} times, total_infer_time {compute_infer_time_ms:<9.2f} ms, avg_infer_time {compute_infer_time_ms:<9.2f}/{batch_count:<5}={compute_infer_time_ms / batch_count:.2f} ms, avg_infer_time_per_sample {compute_infer_time_ms:<9.2f}/{batch_count:<5}/{batch_size}={compute_infer_time_ms / batch_count / batch_size:.2f} ms \n" # noqa + ) + summary_f.write( + f"input {compute_input_time_ms:<9.2f} ms, avg {compute_input_time_ms / batch_count:.2f} ms, " # noqa + ) + summary_f.write( + f"output {compute_output_time_ms:<9.2f} ms, avg {compute_output_time_ms / batch_count:.2f} ms \n" # noqa + ) + + +def get_args(): + parser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter) + + parser.add_argument( + "--server-addr", + type=str, + default="localhost", + help="Address of the server", + ) + + parser.add_argument( + "--server-port", + type=int, + default=8001, + help="Grpc port of the triton server, default is 8001", + ) + + parser.add_argument( + "--reference-audio", + type=str, + default=None, + help="Path to a single audio file. It can't be specified at the same time with --manifest-dir", + ) + + parser.add_argument( + "--reference-text", + type=str, + default="", + help="", + ) + + parser.add_argument( + "--target-text", + type=str, + default="", + help="", + ) + + parser.add_argument( + "--huggingface-dataset", + type=str, + default="yuekai/seed_tts", + help="dataset name in huggingface dataset hub", + ) + + parser.add_argument( + "--split-name", + type=str, + default="wenetspeech4tts", + choices=["wenetspeech4tts", "test_zh", "test_en", "test_hard"], + help="dataset split name, default is 'test'", + ) + + parser.add_argument( + "--manifest-path", + type=str, + default=None, + help="Path to the manifest dir which includes wav.scp trans.txt files.", + ) + + parser.add_argument( + "--model-name", + type=str, + default="f5_tts", + choices=["f5_tts", "spark_tts"], + help="triton model_repo module name to request: transducer for k2, attention_rescoring for wenet offline, streaming_wenet for wenet streaming, infer_pipeline for paraformer large offline", + ) + + parser.add_argument( + "--num-tasks", + type=int, + default=1, + help="Number of concurrent tasks for sending", + ) + + parser.add_argument( + "--log-interval", + type=int, + default=5, + help="Controls how frequently we print the log.", + ) + + parser.add_argument( + "--compute-wer", + action="store_true", + default=False, + help="""True to compute WER. + """, + ) + + parser.add_argument( + "--log-dir", + type=str, + required=False, + default="./tmp", + help="log directory", + ) + + parser.add_argument( + "--batch-size", + type=int, + default=1, + help="Inference batch_size per request for offline mode.", + ) + + return parser.parse_args() + + +def load_audio(wav_path, target_sample_rate=24000): + assert target_sample_rate == 24000, "hard coding in server" + if isinstance(wav_path, dict): + waveform = wav_path["array"] + sample_rate = wav_path["sampling_rate"] + else: + waveform, sample_rate = sf.read(wav_path) + if sample_rate != target_sample_rate: + from scipy.signal import resample + + num_samples = int(len(waveform) * (target_sample_rate / sample_rate)) + waveform = resample(waveform, num_samples) + return waveform, target_sample_rate + + +async def send( + manifest_item_list: list, + name: str, + triton_client: tritonclient.grpc.aio.InferenceServerClient, + protocol_client: types.ModuleType, + log_interval: int, + model_name: str, + padding_duration: int = None, + audio_save_dir: str = "./", + save_sample_rate: int = 24000, +): + total_duration = 0.0 + latency_data = [] + task_id = int(name[5:]) + + print(f"manifest_item_list: {manifest_item_list}") + for i, item in enumerate(manifest_item_list): + if i % log_interval == 0: + print(f"{name}: {i}/{len(manifest_item_list)}") + waveform, sample_rate = load_audio(item["audio_filepath"], target_sample_rate=24000) + duration = len(waveform) / sample_rate + lengths = np.array([[len(waveform)]], dtype=np.int32) + + reference_text, target_text = item["reference_text"], item["target_text"] + + estimated_target_duration = duration / len(reference_text) * len(target_text) + + if padding_duration: + # padding to nearset 10 seconds + samples = np.zeros( + ( + 1, + padding_duration + * sample_rate + * ((int(estimated_target_duration + duration) // padding_duration) + 1), + ), + dtype=np.float32, + ) + + samples[0, : len(waveform)] = waveform + else: + samples = waveform + + samples = samples.reshape(1, -1).astype(np.float32) + + inputs = [ + protocol_client.InferInput("reference_wav", samples.shape, np_to_triton_dtype(samples.dtype)), + protocol_client.InferInput("reference_wav_len", lengths.shape, np_to_triton_dtype(lengths.dtype)), + protocol_client.InferInput("reference_text", [1, 1], "BYTES"), + protocol_client.InferInput("target_text", [1, 1], "BYTES"), + ] + inputs[0].set_data_from_numpy(samples) + inputs[1].set_data_from_numpy(lengths) + + input_data_numpy = np.array([reference_text], dtype=object) + input_data_numpy = input_data_numpy.reshape((1, 1)) + inputs[2].set_data_from_numpy(input_data_numpy) + + input_data_numpy = np.array([target_text], dtype=object) + input_data_numpy = input_data_numpy.reshape((1, 1)) + inputs[3].set_data_from_numpy(input_data_numpy) + + outputs = [protocol_client.InferRequestedOutput("waveform")] + + sequence_id = 100000000 + i + task_id * 10 + start = time.time() + response = await triton_client.infer(model_name, inputs, request_id=str(sequence_id), outputs=outputs) + + audio = response.as_numpy("waveform").reshape(-1) + + end = time.time() - start + + audio_save_path = os.path.join(audio_save_dir, f"{item['target_audio_path']}.wav") + sf.write(audio_save_path, audio, save_sample_rate, "PCM_16") + + actual_duration = len(audio) / save_sample_rate + latency_data.append((end, actual_duration)) + total_duration += actual_duration + + return total_duration, latency_data + + +def load_manifests(manifest_path): + with open(manifest_path, "r") as f: + manifest_list = [] + for line in f: + assert len(line.strip().split("|")) == 4 + utt, prompt_text, prompt_wav, gt_text = line.strip().split("|") + utt = Path(utt).stem + # gt_wav = os.path.join(os.path.dirname(manifest_path), "wavs", utt + ".wav") + if not os.path.isabs(prompt_wav): + prompt_wav = os.path.join(os.path.dirname(manifest_path), prompt_wav) + manifest_list.append( + { + "audio_filepath": prompt_wav, + "reference_text": prompt_text, + "target_text": gt_text, + "target_audio_path": utt, + } + ) + return manifest_list + + +def split_data(data, k): + n = len(data) + if n < k: + print(f"Warning: the length of the input list ({n}) is less than k ({k}). Setting k to {n}.") + k = n + + quotient = n // k + remainder = n % k + + result = [] + start = 0 + for i in range(k): + if i < remainder: + end = start + quotient + 1 + else: + end = start + quotient + + result.append(data[start:end]) + start = end + + return result + + +async def main(): + args = get_args() + url = f"{args.server_addr}:{args.server_port}" + + triton_client = grpcclient.InferenceServerClient(url=url, verbose=False) + protocol_client = grpcclient + + if args.reference_audio: + args.num_tasks = 1 + args.log_interval = 1 + manifest_item_list = [ + { + "reference_text": args.reference_text, + "target_text": args.target_text, + "audio_filepath": args.reference_audio, + "target_audio_path": "test", + } + ] + elif args.huggingface_dataset: + import datasets + + dataset = datasets.load_dataset( + args.huggingface_dataset, + split=args.split_name, + trust_remote_code=True, + ) + manifest_item_list = [] + for i in range(len(dataset)): + manifest_item_list.append( + { + "audio_filepath": dataset[i]["prompt_audio"], + "reference_text": dataset[i]["prompt_text"], + "target_audio_path": dataset[i]["id"], + "target_text": dataset[i]["target_text"], + } + ) + else: + manifest_item_list = load_manifests(args.manifest_path) + + args.num_tasks = min(args.num_tasks, len(manifest_item_list)) + manifest_item_list = split_data(manifest_item_list, args.num_tasks) + + os.makedirs(args.log_dir, exist_ok=True) + tasks = [] + start_time = time.time() + for i in range(args.num_tasks): + task = asyncio.create_task( + send( + manifest_item_list[i], + name=f"task-{i}", + triton_client=triton_client, + protocol_client=protocol_client, + log_interval=args.log_interval, + model_name=args.model_name, + audio_save_dir=args.log_dir, + padding_duration=1, + save_sample_rate=24000, + ) + ) + tasks.append(task) + + ans_list = await asyncio.gather(*tasks) + + end_time = time.time() + elapsed = end_time - start_time + + total_duration = 0.0 + latency_data = [] + for ans in ans_list: + total_duration += ans[0] + latency_data += ans[1] + + rtf = elapsed / total_duration + + s = f"RTF: {rtf:.4f}\n" + s += f"total_duration: {total_duration:.3f} seconds\n" + s += f"({total_duration / 3600:.2f} hours)\n" + s += f"processing time: {elapsed:.3f} seconds ({elapsed / 3600:.2f} hours)\n" + + latency_list = [chunk_end for (chunk_end, chunk_duration) in latency_data] + latency_ms = sum(latency_list) / float(len(latency_list)) * 1000.0 + latency_variance = np.var(latency_list, dtype=np.float64) * 1000.0 + s += f"latency_variance: {latency_variance:.2f}\n" + s += f"latency_50_percentile_ms: {np.percentile(latency_list, 50) * 1000.0:.2f}\n" + s += f"latency_90_percentile_ms: {np.percentile(latency_list, 90) * 1000.0:.2f}\n" + s += f"latency_95_percentile_ms: {np.percentile(latency_list, 95) * 1000.0:.2f}\n" + s += f"latency_99_percentile_ms: {np.percentile(latency_list, 99) * 1000.0:.2f}\n" + s += f"average_latency_ms: {latency_ms:.2f}\n" + + print(s) + if args.manifest_path: + name = Path(args.manifest_path).stem + elif args.split_name: + name = args.split_name + with open(f"{args.log_dir}/rtf-{name}.txt", "w") as f: + f.write(s) + + stats = await triton_client.get_inference_statistics(model_name="", as_json=True) + write_triton_stats(stats, f"{args.log_dir}/stats_summary-{name}.txt") + + metadata = await triton_client.get_model_config(model_name=args.model_name, as_json=True) + with open(f"{args.log_dir}/model_config-{name}.json", "w") as f: + json.dump(metadata, f, indent=4) + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/src/f5_tts/src/f5_tts/runtime/triton_trtllm/client_http.py b/src/f5_tts/src/f5_tts/runtime/triton_trtllm/client_http.py new file mode 100644 index 0000000000000000000000000000000000000000..44c33a2070b8d3a66b3a1587c74c6961e57c54f7 --- /dev/null +++ b/src/f5_tts/src/f5_tts/runtime/triton_trtllm/client_http.py @@ -0,0 +1,143 @@ +# Copyright 2025, NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# * Neither the name of NVIDIA CORPORATION nor the names of its +# contributors may be used to endorse or promote products derived +# from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY +# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY +# OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +import argparse + +import numpy as np +import requests +import soundfile as sf + + +def get_args(): + parser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter) + + parser.add_argument( + "--server-url", + type=str, + default="localhost:8000", + help="Address of the server", + ) + + parser.add_argument( + "--reference-audio", + type=str, + default="../../infer/examples/basic/basic_ref_en.wav", + help="Path to a single audio file. It can't be specified at the same time with --manifest-dir", + ) + + parser.add_argument( + "--reference-text", + type=str, + default="Some call me nature, others call me mother nature.", + help="", + ) + + parser.add_argument( + "--target-text", + type=str, + default="I don't really care what you call me. I've been a silent spectator, watching species evolve, empires rise and fall. But always remember, I am mighty and enduring.", + help="", + ) + + parser.add_argument( + "--model-name", + type=str, + default="f5_tts", + choices=["f5_tts", "spark_tts"], + help="triton model_repo module name to request", + ) + + parser.add_argument( + "--output-audio", + type=str, + default="output.wav", + help="Path to save the output audio", + ) + return parser.parse_args() + + +def prepare_request( + samples, + reference_text, + target_text, + sample_rate=24000, + audio_save_dir: str = "./", +): + assert len(samples.shape) == 1, "samples should be 1D" + lengths = np.array([[len(samples)]], dtype=np.int32) + samples = samples.reshape(1, -1).astype(np.float32) + + data = { + "inputs": [ + {"name": "reference_wav", "shape": samples.shape, "datatype": "FP32", "data": samples.tolist()}, + { + "name": "reference_wav_len", + "shape": lengths.shape, + "datatype": "INT32", + "data": lengths.tolist(), + }, + {"name": "reference_text", "shape": [1, 1], "datatype": "BYTES", "data": [reference_text]}, + {"name": "target_text", "shape": [1, 1], "datatype": "BYTES", "data": [target_text]}, + ] + } + + return data + + +def load_audio(wav_path, target_sample_rate=24000): + assert target_sample_rate == 24000, "hard coding in server" + if isinstance(wav_path, dict): + samples = wav_path["array"] + sample_rate = wav_path["sampling_rate"] + else: + samples, sample_rate = sf.read(wav_path) + if sample_rate != target_sample_rate: + from scipy.signal import resample + + num_samples = int(len(samples) * (target_sample_rate / sample_rate)) + samples = resample(samples, num_samples) + return samples, target_sample_rate + + +if __name__ == "__main__": + args = get_args() + server_url = args.server_url + if not server_url.startswith(("http://", "https://")): + server_url = f"http://{server_url}" + + url = f"{server_url}/v2/models/{args.model_name}/infer" + samples, sr = load_audio(args.reference_audio) + assert sr == 24000, "sample rate hardcoded in server" + + samples = np.array(samples, dtype=np.float32) + data = prepare_request(samples, args.reference_text, args.target_text) + + rsp = requests.post( + url, headers={"Content-Type": "application/json"}, json=data, verify=False, params={"request_id": "0"} + ) + result = rsp.json() + audio = result["outputs"][0]["data"] + audio = np.array(audio, dtype=np.float32) + sf.write(args.output_audio, audio, 24000, "PCM_16") diff --git a/src/f5_tts/src/f5_tts/runtime/triton_trtllm/docker-compose.yml b/src/f5_tts/src/f5_tts/runtime/triton_trtllm/docker-compose.yml new file mode 100644 index 0000000000000000000000000000000000000000..21c4391b0500868b1c511fe22ae7f5534b3cc034 --- /dev/null +++ b/src/f5_tts/src/f5_tts/runtime/triton_trtllm/docker-compose.yml @@ -0,0 +1,20 @@ +services: + tts: + image: soar97/triton-f5-tts:24.12 + shm_size: '1gb' + ports: + - "8000:8000" + - "8001:8001" + - "8002:8002" + environment: + - PYTHONIOENCODING=utf-8 + - MODEL_ID=${MODEL_ID} + deploy: + resources: + reservations: + devices: + - driver: nvidia + device_ids: ['0'] + capabilities: [gpu] + command: > + /bin/bash -c "pip install vocos && rm -rf F5-TTS && git clone https://github.com/SWivid/F5-TTS.git && cd F5-TTS/src/f5_tts/runtime/triton_trtllm/ && bash run.sh 0 4 $MODEL" diff --git a/src/f5_tts/src/f5_tts/runtime/triton_trtllm/model_repo_f5_tts/f5_tts/1/f5_tts_trtllm.py b/src/f5_tts/src/f5_tts/runtime/triton_trtllm/model_repo_f5_tts/f5_tts/1/f5_tts_trtllm.py new file mode 100644 index 0000000000000000000000000000000000000000..466fa61ab513cfc459f5ad16bff3c4c43639ea0c --- /dev/null +++ b/src/f5_tts/src/f5_tts/runtime/triton_trtllm/model_repo_f5_tts/f5_tts/1/f5_tts_trtllm.py @@ -0,0 +1,430 @@ +import math +import os +import time +from functools import wraps +from typing import List, Optional + +import tensorrt as trt +import tensorrt_llm +import torch +import torch.nn as nn +import torch.nn.functional as F +from tensorrt_llm._utils import str_dtype_to_torch, trt_dtype_to_torch +from tensorrt_llm.logger import logger +from tensorrt_llm.runtime.session import Session + + +def remove_tensor_padding(input_tensor, input_tensor_lengths=None): + # Audio tensor case: batch, seq_len, feature_len + # position_ids case: batch, seq_len + assert input_tensor_lengths is not None, "input_tensor_lengths must be provided for 3D input_tensor" + + # Initialize a list to collect valid sequences + valid_sequences = [] + + for i in range(input_tensor.shape[0]): + valid_length = input_tensor_lengths[i] + valid_sequences.append(input_tensor[i, :valid_length]) + + # Concatenate all valid sequences along the batch dimension + output_tensor = torch.cat(valid_sequences, dim=0).contiguous() + return output_tensor + + +class TextEmbedding(nn.Module): + def __init__(self, text_num_embeds, text_dim, conv_layers=0, conv_mult=2, precompute_max_pos=4096): + super().__init__() + self.text_embed = nn.Embedding(text_num_embeds + 1, text_dim) # use 0 as filler token + self.register_buffer("freqs_cis", precompute_freqs_cis(text_dim, precompute_max_pos), persistent=False) + self.text_blocks = nn.Sequential(*[ConvNeXtV2Block(text_dim, text_dim * conv_mult) for _ in range(conv_layers)]) + + def forward(self, text): + # only keep tensors with value not -1 + text_mask = text != -1 + text_pad_cut_off_index = text_mask.sum(dim=1).max() + + text = text[:, :text_pad_cut_off_index] + text = self.text_embed(text) + text = text + self.freqs_cis[: text.shape[1], :] + for block in self.text_blocks: + text = block(text) + # padding text to the original length + # text shape: B,seq_len,C + # pad at the second dimension + text = F.pad(text, (0, 0, 0, text_mask.shape[1] - text.shape[1], 0, 0), value=0) + return text + + +class GRN(nn.Module): + def __init__(self, dim): + super().__init__() + self.gamma = nn.Parameter(torch.zeros(1, 1, dim)) + self.beta = nn.Parameter(torch.zeros(1, 1, dim)) + + def forward(self, x): + Gx = torch.norm(x, p=2, dim=1, keepdim=True) + Nx = Gx / (Gx.mean(dim=-1, keepdim=True) + 1e-6) + return self.gamma * (x * Nx) + self.beta + x + + +class ConvNeXtV2Block(nn.Module): + def __init__( + self, + dim: int, + intermediate_dim: int, + dilation: int = 1, + ): + super().__init__() + padding = (dilation * (7 - 1)) // 2 + self.dwconv = nn.Conv1d( + dim, dim, kernel_size=7, padding=padding, groups=dim, dilation=dilation + ) # depthwise conv + self.norm = nn.LayerNorm(dim, eps=1e-6) + self.pwconv1 = nn.Linear(dim, intermediate_dim) # pointwise/1x1 convs, implemented with linear layers + self.act = nn.GELU() + self.grn = GRN(intermediate_dim) + self.pwconv2 = nn.Linear(intermediate_dim, dim) + + def forward(self, x: torch.Tensor) -> torch.Tensor: + residual = x + x = x.transpose(1, 2) # b n d -> b d n + x = self.dwconv(x) + x = x.transpose(1, 2) # b d n -> b n d + x = self.norm(x) + x = self.pwconv1(x) + x = self.act(x) + x = self.grn(x) + x = self.pwconv2(x) + return residual + x + + +def precompute_freqs_cis(dim: int, end: int, theta: float = 10000.0, theta_rescale_factor=1.0): + # proposed by reddit user bloc97, to rescale rotary embeddings to longer sequence length without fine-tuning + # has some connection to NTK literature + # https://www.reddit.com/r/LocalLLaMA/comments/14lz7j5/ntkaware_scaled_rope_allows_llama_models_to_have/ + # https://github.com/lucidrains/rotary-embedding-torch/blob/main/rotary_embedding_torch/rotary_embedding_torch.py + theta *= theta_rescale_factor ** (dim / (dim - 2)) + freqs = 1.0 / (theta ** (torch.arange(0, dim, 2)[: (dim // 2)].float() / dim)) + t = torch.arange(end, device=freqs.device) # type: ignore + freqs = torch.outer(t, freqs).float() # type: ignore + freqs_cos = torch.cos(freqs) # real part + freqs_sin = torch.sin(freqs) # imaginary part + return torch.cat([freqs_cos, freqs_sin], dim=-1) + + +def load_checkpoint(ckpt_path, use_ema=True): + checkpoint = torch.load(ckpt_path, weights_only=True) + if use_ema: + checkpoint["model_state_dict"] = { + k.replace("ema_model.", ""): v + for k, v in checkpoint["ema_model_state_dict"].items() + if k not in ["initted", "step"] + } + dict_state = checkpoint["model_state_dict"] + text_embed_dict = {} + for key in dict_state.keys(): + # transformer.text_embed.text_embed.weight -> text_embed.weight + if "text_embed" in key: + text_embed_dict[key.replace("transformer.text_embed.", "")] = dict_state[key] + return text_embed_dict + + +class F5TTS(object): + def __init__( + self, + config, + debug_mode=True, + stream: Optional[torch.cuda.Stream] = None, + tllm_model_dir: Optional[str] = None, + model_path: Optional[str] = None, + vocab_size: Optional[int] = None, + ): + self.dtype = config["pretrained_config"]["dtype"] + + rank = tensorrt_llm.mpi_rank() + world_size = config["pretrained_config"]["mapping"]["world_size"] + cp_size = config["pretrained_config"]["mapping"]["cp_size"] + tp_size = config["pretrained_config"]["mapping"]["tp_size"] + pp_size = config["pretrained_config"]["mapping"]["pp_size"] + assert pp_size == 1 + self.mapping = tensorrt_llm.Mapping( + world_size=world_size, rank=rank, cp_size=cp_size, tp_size=tp_size, pp_size=1, gpus_per_node=1 + ) + + local_rank = rank % self.mapping.gpus_per_node + self.device = torch.device(f"cuda:{local_rank}") + + torch.cuda.set_device(self.device) + + self.stream = stream + if self.stream is None: + self.stream = torch.cuda.Stream(self.device) + torch.cuda.set_stream(self.stream) + + engine_file = os.path.join(tllm_model_dir, f"rank{rank}.engine") + logger.info(f"Loading engine from {engine_file}") + with open(engine_file, "rb") as f: + engine_buffer = f.read() + + assert engine_buffer is not None + + self.session = Session.from_serialized_engine(engine_buffer) + + self.debug_mode = debug_mode + + self.inputs = {} + self.outputs = {} + self.buffer_allocated = False + + expected_tensor_names = ["noise", "cond", "time", "rope_cos", "rope_sin", "input_lengths", "denoised"] + + found_tensor_names = [self.session.engine.get_tensor_name(i) for i in range(self.session.engine.num_io_tensors)] + if not self.debug_mode and set(expected_tensor_names) != set(found_tensor_names): + logger.error( + f"The following expected tensors are not found: {set(expected_tensor_names).difference(set(found_tensor_names))}" + ) + logger.error( + f"Those tensors in engine are not expected: {set(found_tensor_names).difference(set(expected_tensor_names))}" + ) + logger.error(f"Expected tensor names: {expected_tensor_names}") + logger.error(f"Found tensor names: {found_tensor_names}") + raise RuntimeError("Tensor names in engine are not the same as expected.") + if self.debug_mode: + self.debug_tensors = list(set(found_tensor_names) - set(expected_tensor_names)) + + self.max_mel_len = 4096 + self.text_embedding = TextEmbedding( + text_num_embeds=vocab_size, text_dim=512, conv_layers=4, precompute_max_pos=self.max_mel_len + ).to(self.device) + self.text_embedding.load_state_dict(load_checkpoint(model_path), strict=True) + + self.target_audio_sample_rate = 24000 + self.target_rms = 0.15 # target rms for audio + self.n_fft = 1024 + self.win_length = 1024 + self.hop_length = 256 + self.n_mel_channels = 100 + # self.max_mel_len = 3000 + self.head_dim = 64 + self.base_rescale_factor = 1.0 + self.interpolation_factor = 1.0 + base = 10000.0 * self.base_rescale_factor ** (self.head_dim / (self.head_dim - 2)) + inv_freq = 1.0 / (base ** (torch.arange(0, self.head_dim, 2).float() / self.head_dim)) + freqs = torch.outer(torch.arange(self.max_mel_len, dtype=torch.float32), inv_freq) / self.interpolation_factor + self.freqs = freqs.repeat_interleave(2, dim=-1).unsqueeze(0) + self.rope_cos = self.freqs.cos().half() + self.rope_sin = self.freqs.sin().half() + self.nfe_steps = 16 + t = torch.linspace(0, 1, self.nfe_steps + 1, dtype=torch.float32) + time_step = t + (-1.0) * (torch.cos(torch.pi * 0.5 * t) - 1 + t) + delta_t = torch.diff(time_step) + # WAR: hard coding 256 here + tmp_dim = 256 + time_expand = torch.zeros((1, self.nfe_steps, tmp_dim), dtype=torch.float32) + half_dim = tmp_dim // 2 + emb_factor = math.log(10000) / (half_dim - 1) + emb_factor = 1000.0 * torch.exp(torch.arange(half_dim, dtype=torch.float32) * -emb_factor) + for i in range(self.nfe_steps): + emb = time_step[i] * emb_factor + time_expand[:, i, :] = torch.cat((emb.sin(), emb.cos()), dim=-1) + self.time_expand = time_expand.to(self.device) + self.delta_t = torch.cat((delta_t, delta_t), dim=0).contiguous().to(self.device) + + def _tensor_dtype(self, name): + # return torch dtype given tensor name for convenience + dtype = trt_dtype_to_torch(self.session.engine.get_tensor_dtype(name)) + return dtype + + def _setup(self, batch_size, seq_len): + for i in range(self.session.engine.num_io_tensors): + name = self.session.engine.get_tensor_name(i) + if self.session.engine.get_tensor_mode(name) == trt.TensorIOMode.OUTPUT: + shape = list(self.session.engine.get_tensor_shape(name)) + shape[0] = batch_size + shape[1] = seq_len + self.outputs[name] = torch.empty(shape, dtype=self._tensor_dtype(name), device=self.device) + + self.buffer_allocated = True + + def cuda_stream_guard(func): + """Sync external stream and set current stream to the one bound to the session. Reset on exit.""" + + @wraps(func) + def wrapper(self, *args, **kwargs): + external_stream = torch.cuda.current_stream() + if external_stream != self.stream: + external_stream.synchronize() + torch.cuda.set_stream(self.stream) + ret = func(self, *args, **kwargs) + if external_stream != self.stream: + self.stream.synchronize() + torch.cuda.set_stream(external_stream) + return ret + + return wrapper + + @cuda_stream_guard + def forward( + self, + noise: torch.Tensor, + cond: torch.Tensor, + time_expand: torch.Tensor, + rope_cos: torch.Tensor, + rope_sin: torch.Tensor, + input_lengths: torch.Tensor, + delta_t: torch.Tensor, + use_perf: bool = False, + ): + if use_perf: + torch.cuda.nvtx.range_push("flow matching") + cfg_strength = 2.0 + batch_size = noise.shape[0] + half_batch = batch_size // 2 + noise_half = noise[:half_batch] # Store the initial half of noise + + input_type = str_dtype_to_torch(self.dtype) + + # Keep a copy of the initial tensors + cond = cond.to(input_type) + rope_cos = rope_cos.to(input_type) + rope_sin = rope_sin.to(input_type) + input_lengths = input_lengths.to(str_dtype_to_torch("int32")) + + # Instead of iteratively updating noise within a single model context, + # we'll do a single forward pass for each iteration with fresh context setup + for i in range(self.nfe_steps): + # Re-setup the buffers for clean execution + self._setup(batch_size, noise.shape[1]) + if not self.buffer_allocated: + raise RuntimeError("Buffer not allocated, please call setup first!") + + # Re-create combined noises for this iteration + current_noise = torch.cat([noise_half, noise_half], dim=0).to(input_type) + + # Get time step for this iteration + current_time = time_expand[:, i].to(input_type) + + # Create fresh input dictionary for this iteration + current_inputs = { + "noise": current_noise, + "cond": cond, + "time": current_time, + "rope_cos": rope_cos, + "rope_sin": rope_sin, + "input_lengths": input_lengths, + } + + # Update inputs and set shapes + self.inputs.clear() # Clear previous inputs + self.inputs.update(**current_inputs) + self.session.set_shapes(self.inputs) + + if use_perf: + torch.cuda.nvtx.range_push(f"execute {i}") + ok = self.session.run(self.inputs, self.outputs, self.stream.cuda_stream) + assert ok, "Failed to execute model" + # self.session.context.execute_async_v3(self.stream.cuda_stream) + if use_perf: + torch.cuda.nvtx.range_pop() + # Process results + t_scale = delta_t[i].unsqueeze(0).to(input_type) + + # Extract predictions + pred_cond = self.outputs["denoised"][:half_batch] + pred_uncond = self.outputs["denoised"][half_batch:] + + # Apply classifier-free guidance with safeguards + guidance = pred_cond + (pred_cond - pred_uncond) * cfg_strength + # Calculate update for noise + noise_half = noise_half + guidance * t_scale + if use_perf: + torch.cuda.nvtx.range_pop() + return noise_half + + def sample( + self, + text_pad_sequence: torch.Tensor, + ref_mel_batch: torch.Tensor, + ref_mel_len_batch: torch.Tensor, + estimated_reference_target_mel_len: List[int], + remove_input_padding: bool = False, + use_perf: bool = False, + ): + if use_perf: + torch.cuda.nvtx.range_push("text embedding") + batch = text_pad_sequence.shape[0] + max_seq_len = ref_mel_batch.shape[1] + + text_pad_sequence_drop = torch.cat( + (text_pad_sequence, torch.zeros((1, text_pad_sequence.shape[1]), dtype=torch.int32).to(self.device)), dim=0 + ) + + text_embedding_drop_list = [] + for i in range(batch + 1): + text_embedding_drop_list.append(self.text_embedding(text_pad_sequence_drop[i].unsqueeze(0).to(self.device))) + text_embedding_drop_condition = torch.cat(text_embedding_drop_list, dim=0) + + text_embedding = text_embedding_drop_condition[:-1] + # text_embedding_drop B,T,C batch should be the same + text_embedding_drop = text_embedding_drop_condition[-1].unsqueeze(0).repeat(batch, 1, 1) + + noise = torch.randn_like(ref_mel_batch).to(self.device) + rope_cos = self.rope_cos[:, :max_seq_len, :].float().repeat(batch, 1, 1) + rope_sin = self.rope_sin[:, :max_seq_len, :].float().repeat(batch, 1, 1) + + cat_mel_text = torch.cat((ref_mel_batch, text_embedding), dim=-1) + cat_mel_text_drop = torch.cat( + ( + torch.zeros((batch, max_seq_len, self.n_mel_channels), dtype=torch.float32).to(self.device), + text_embedding_drop, + ), + dim=-1, + ) + + time_expand = self.time_expand.repeat(2 * batch, 1, 1).contiguous() + + # Convert estimated_reference_target_mel_len to tensor + input_lengths = torch.tensor(estimated_reference_target_mel_len, dtype=torch.int32) + + # combine above along the batch dimension + inputs = { + "noise": torch.cat((noise, noise), dim=0).contiguous(), + "cond": torch.cat((cat_mel_text, cat_mel_text_drop), dim=0).contiguous(), + "time_expand": time_expand, + "rope_cos": torch.cat((rope_cos, rope_cos), dim=0).contiguous(), + "rope_sin": torch.cat((rope_sin, rope_sin), dim=0).contiguous(), + "input_lengths": torch.cat((input_lengths, input_lengths), dim=0).contiguous(), + "delta_t": self.delta_t, + } + if use_perf and remove_input_padding: + torch.cuda.nvtx.range_push("remove input padding") + if remove_input_padding: + max_seq_len = inputs["cond"].shape[1] + inputs["noise"] = remove_tensor_padding(inputs["noise"], inputs["input_lengths"]) + inputs["cond"] = remove_tensor_padding(inputs["cond"], inputs["input_lengths"]) + # for time_expand, convert from B,D to B,T,D by repeat + inputs["time_expand"] = inputs["time_expand"].unsqueeze(1).repeat(1, max_seq_len, 1, 1) + inputs["time_expand"] = remove_tensor_padding(inputs["time_expand"], inputs["input_lengths"]) + inputs["rope_cos"] = remove_tensor_padding(inputs["rope_cos"], inputs["input_lengths"]) + inputs["rope_sin"] = remove_tensor_padding(inputs["rope_sin"], inputs["input_lengths"]) + if use_perf and remove_input_padding: + torch.cuda.nvtx.range_pop() + for key in inputs: + inputs[key] = inputs[key].to(self.device) + if use_perf: + torch.cuda.nvtx.range_pop() + start_time = time.time() + denoised = self.forward(**inputs, use_perf=use_perf) + cost_time = time.time() - start_time + if use_perf and remove_input_padding: + torch.cuda.nvtx.range_push("remove input padding output") + if remove_input_padding: + denoised_list = [] + start_idx = 0 + for i in range(batch): + denoised_list.append(denoised[start_idx : start_idx + inputs["input_lengths"][i]]) + start_idx += inputs["input_lengths"][i] + if use_perf and remove_input_padding: + torch.cuda.nvtx.range_pop() + return denoised_list, cost_time + return denoised, cost_time diff --git a/src/f5_tts/src/f5_tts/runtime/triton_trtllm/model_repo_f5_tts/f5_tts/1/model.py b/src/f5_tts/src/f5_tts/runtime/triton_trtllm/model_repo_f5_tts/f5_tts/1/model.py new file mode 100644 index 0000000000000000000000000000000000000000..c449f75cef68e17e46ccffd4e343e88745efce03 --- /dev/null +++ b/src/f5_tts/src/f5_tts/runtime/triton_trtllm/model_repo_f5_tts/f5_tts/1/model.py @@ -0,0 +1,278 @@ +# Copyright 2025, NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# * Neither the name of NVIDIA CORPORATION nor the names of its +# contributors may be used to endorse or promote products derived +# from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY +# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY +# OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +import json +import os + +import jieba +import torch +import torch.nn.functional as F +import torchaudio +import triton_python_backend_utils as pb_utils +from f5_tts_trtllm import F5TTS +from pypinyin import Style, lazy_pinyin +from torch.nn.utils.rnn import pad_sequence +from torch.utils.dlpack import from_dlpack, to_dlpack + + +def get_tokenizer(vocab_file_path: str): + """ + tokenizer - "pinyin" do g2p for only chinese characters, need .txt vocab_file + - "char" for char-wise tokenizer, need .txt vocab_file + - "byte" for utf-8 tokenizer + - "custom" if you're directly passing in a path to the vocab.txt you want to use + vocab_size - if use "pinyin", all available pinyin types, common alphabets (also those with accent) and symbols + - if use "char", derived from unfiltered character & symbol counts of custom dataset + - if use "byte", set to 256 (unicode byte range) + """ + with open(vocab_file_path, "r", encoding="utf-8") as f: + vocab_char_map = {} + for i, char in enumerate(f): + vocab_char_map[char[:-1]] = i + vocab_size = len(vocab_char_map) + return vocab_char_map, vocab_size + + +def convert_char_to_pinyin(reference_target_texts_list, polyphone=True): + final_reference_target_texts_list = [] + custom_trans = str.maketrans( + {";": ",", "“": '"', "”": '"', "‘": "'", "’": "'"} + ) # add custom trans here, to address oov + + def is_chinese(c): + return "\u3100" <= c <= "\u9fff" # common chinese characters + + for text in reference_target_texts_list: + char_list = [] + text = text.translate(custom_trans) + for seg in jieba.cut(text): + seg_byte_len = len(bytes(seg, "UTF-8")) + if seg_byte_len == len(seg): # if pure alphabets and symbols + if char_list and seg_byte_len > 1 and char_list[-1] not in " :'\"": + char_list.append(" ") + char_list.extend(seg) + elif polyphone and seg_byte_len == 3 * len(seg): # if pure east asian characters + seg_ = lazy_pinyin(seg, style=Style.TONE3, tone_sandhi=True) + for i, c in enumerate(seg): + if is_chinese(c): + char_list.append(" ") + char_list.append(seg_[i]) + else: # if mixed characters, alphabets and symbols + for c in seg: + if ord(c) < 256: + char_list.extend(c) + elif is_chinese(c): + char_list.append(" ") + char_list.extend(lazy_pinyin(c, style=Style.TONE3, tone_sandhi=True)) + else: + char_list.append(c) + final_reference_target_texts_list.append(char_list) + + return final_reference_target_texts_list + + +def list_str_to_idx( + text: list[str] | list[list[str]], + vocab_char_map: dict[str, int], # {char: idx} + padding_value=-1, +): # noqa: F722 + list_idx_tensors = [torch.tensor([vocab_char_map.get(c, 0) for c in t]) for t in text] # pinyin or char style + return list_idx_tensors + + +class TritonPythonModel: + def initialize(self, args): + self.use_perf = True + self.device = torch.device("cuda") + self.target_audio_sample_rate = 24000 + self.target_rms = 0.15 # target rms for audio + self.n_fft = 1024 + self.win_length = 1024 + self.hop_length = 256 + self.n_mel_channels = 100 + self.max_mel_len = 3000 + self.head_dim = 64 + + parameters = json.loads(args["model_config"])["parameters"] + for key, value in parameters.items(): + parameters[key] = value["string_value"] + + self.vocab_char_map, self.vocab_size = get_tokenizer(parameters["vocab_file"]) + self.reference_sample_rate = int(parameters["reference_audio_sample_rate"]) + self.resampler = torchaudio.transforms.Resample(self.reference_sample_rate, self.target_audio_sample_rate) + + self.tllm_model_dir = parameters["tllm_model_dir"] + config_file = os.path.join(self.tllm_model_dir, "config.json") + with open(config_file) as f: + config = json.load(f) + self.model = F5TTS( + config, + debug_mode=False, + tllm_model_dir=self.tllm_model_dir, + model_path=parameters["model_path"], + vocab_size=self.vocab_size, + ) + + self.vocoder = parameters["vocoder"] + assert self.vocoder in ["vocos", "bigvgan"] + if self.vocoder == "vocos": + self.mel_stft = torchaudio.transforms.MelSpectrogram( + sample_rate=self.target_audio_sample_rate, + n_fft=self.n_fft, + win_length=self.win_length, + hop_length=self.hop_length, + n_mels=self.n_mel_channels, + power=1, + center=True, + normalized=False, + norm=None, + ).to(self.device) + self.compute_mel_fn = self.get_vocos_mel_spectrogram + elif self.vocoder == "bigvgan": + self.compute_mel_fn = self.get_bigvgan_mel_spectrogram + + def get_vocos_mel_spectrogram(self, waveform): + mel = self.mel_stft(waveform) + mel = mel.clamp(min=1e-5).log() + return mel.transpose(1, 2) + + def forward_vocoder(self, mel): + mel = mel.to(torch.float32).contiguous().cpu() + input_tensor_0 = pb_utils.Tensor.from_dlpack("mel", to_dlpack(mel)) + + inference_request = pb_utils.InferenceRequest( + model_name="vocoder", requested_output_names=["waveform"], inputs=[input_tensor_0] + ) + inference_response = inference_request.exec() + if inference_response.has_error(): + raise pb_utils.TritonModelException(inference_response.error().message()) + else: + waveform = pb_utils.get_output_tensor_by_name(inference_response, "waveform") + waveform = torch.utils.dlpack.from_dlpack(waveform.to_dlpack()).cpu() + + return waveform + + def execute(self, requests): + ( + reference_text_list, + target_text_list, + reference_target_texts_list, + estimated_reference_target_mel_len, + reference_mel_len, + ) = [], [], [], [], [] + mel_features_list = [] + if self.use_perf: + torch.cuda.nvtx.range_push("preprocess") + for request in requests: + wav_tensor = pb_utils.get_input_tensor_by_name(request, "reference_wav") + wav_lens = pb_utils.get_input_tensor_by_name(request, "reference_wav_len") + + reference_text = pb_utils.get_input_tensor_by_name(request, "reference_text").as_numpy() + reference_text = reference_text[0][0].decode("utf-8") + reference_text_list.append(reference_text) + target_text = pb_utils.get_input_tensor_by_name(request, "target_text").as_numpy() + target_text = target_text[0][0].decode("utf-8") + target_text_list.append(target_text) + + text = reference_text + target_text + reference_target_texts_list.append(text) + + wav = from_dlpack(wav_tensor.to_dlpack()) + wav_len = from_dlpack(wav_lens.to_dlpack()) + wav_len = wav_len.squeeze() + assert wav.shape[0] == 1, "Only support batch size 1 for now." + wav = wav[:, :wav_len] + + ref_rms = torch.sqrt(torch.mean(torch.square(wav))) + if ref_rms < self.target_rms: + wav = wav * self.target_rms / ref_rms + if self.reference_sample_rate != self.target_audio_sample_rate: + wav = self.resampler(wav) + wav = wav.to(self.device) + if self.use_perf: + torch.cuda.nvtx.range_push("compute_mel") + mel_features = self.compute_mel_fn(wav) + if self.use_perf: + torch.cuda.nvtx.range_pop() + mel_features_list.append(mel_features) + + reference_mel_len.append(mel_features.shape[1]) + estimated_reference_target_mel_len.append( + int( + mel_features.shape[1] * (1 + len(target_text.encode("utf-8")) / len(reference_text.encode("utf-8"))) + ) + ) + + max_seq_len = min(max(estimated_reference_target_mel_len), self.max_mel_len) + + batch = len(requests) + mel_features = torch.zeros((batch, max_seq_len, self.n_mel_channels), dtype=torch.float16).to(self.device) + for i, mel in enumerate(mel_features_list): + mel_features[i, : mel.shape[1], :] = mel + + reference_mel_len_tensor = torch.LongTensor(reference_mel_len).to(self.device) + + pinyin_list = convert_char_to_pinyin(reference_target_texts_list, polyphone=True) + text_pad_sequence = list_str_to_idx(pinyin_list, self.vocab_char_map) + + for i, item in enumerate(text_pad_sequence): + text_pad_sequence[i] = F.pad( + item, (0, estimated_reference_target_mel_len[i] - len(item)), mode="constant", value=-1 + ) + text_pad_sequence[i] += 1 # WAR: 0 is reserved for padding token, hard coding in F5-TTS + text_pad_sequence = pad_sequence(text_pad_sequence, padding_value=-1, batch_first=True).to(self.device) + text_pad_sequence = F.pad( + text_pad_sequence, (0, max_seq_len - text_pad_sequence.shape[1]), mode="constant", value=-1 + ) + if self.use_perf: + torch.cuda.nvtx.range_pop() + + denoised, cost_time = self.model.sample( + text_pad_sequence, + mel_features, + reference_mel_len_tensor, + estimated_reference_target_mel_len, + remove_input_padding=False, + use_perf=self.use_perf, + ) + if self.use_perf: + torch.cuda.nvtx.range_push("vocoder") + + responses = [] + for i in range(batch): + ref_me_len = reference_mel_len[i] + estimated_mel_len = estimated_reference_target_mel_len[i] + denoised_one_item = denoised[i, ref_me_len:estimated_mel_len, :].unsqueeze(0).transpose(1, 2) + audio = self.forward_vocoder(denoised_one_item) + rms = torch.sqrt(torch.mean(torch.square(audio))) + if rms < self.target_rms: + audio = audio * self.target_rms / rms + + audio = pb_utils.Tensor.from_dlpack("waveform", to_dlpack(audio)) + inference_response = pb_utils.InferenceResponse(output_tensors=[audio]) + responses.append(inference_response) + if self.use_perf: + torch.cuda.nvtx.range_pop() + return responses diff --git a/src/f5_tts/src/f5_tts/runtime/triton_trtllm/model_repo_f5_tts/f5_tts/config.pbtxt b/src/f5_tts/src/f5_tts/runtime/triton_trtllm/model_repo_f5_tts/f5_tts/config.pbtxt new file mode 100644 index 0000000000000000000000000000000000000000..e40076f510e8e7464cfef408d9f49bc0bb74284f --- /dev/null +++ b/src/f5_tts/src/f5_tts/runtime/triton_trtllm/model_repo_f5_tts/f5_tts/config.pbtxt @@ -0,0 +1,81 @@ +# Copyright (c) 2025, NVIDIA CORPORATION. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +name: "f5_tts" +backend: "python" +max_batch_size: 4 +dynamic_batching { + max_queue_delay_microseconds: 1000 +} +parameters [ + { + key: "vocab_file" + value: { string_value: "${vocab}"} + }, + { + key: "model_path", + value: {string_value:"${model}"} + }, + { + key: "tllm_model_dir", + value: {string_value:"${trtllm}"} + }, + { + key: "reference_audio_sample_rate", + value: {string_value:"24000"} + }, + { + key: "vocoder", + value: {string_value:"${vocoder}"} + } +] + +input [ + { + name: "reference_wav" + data_type: TYPE_FP32 + dims: [-1] + optional: True + }, + { + name: "reference_wav_len" + data_type: TYPE_INT32 + dims: [1] + optional: True + }, + { + name: "reference_text" + data_type: TYPE_STRING + dims: [1] + }, + { + name: "target_text" + data_type: TYPE_STRING + dims: [1] + } +] +output [ + { + name: "waveform" + data_type: TYPE_FP32 + dims: [ -1 ] + } +] + +instance_group [ + { + count: 1 + kind: KIND_GPU + } +] \ No newline at end of file diff --git a/src/f5_tts/src/f5_tts/runtime/triton_trtllm/model_repo_f5_tts/vocoder/1/.gitkeep b/src/f5_tts/src/f5_tts/runtime/triton_trtllm/model_repo_f5_tts/vocoder/1/.gitkeep new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/src/f5_tts/src/f5_tts/runtime/triton_trtllm/model_repo_f5_tts/vocoder/config.pbtxt b/src/f5_tts/src/f5_tts/runtime/triton_trtllm/model_repo_f5_tts/vocoder/config.pbtxt new file mode 100644 index 0000000000000000000000000000000000000000..641e06a251fa40500a1f0b0b18d6328603865acc --- /dev/null +++ b/src/f5_tts/src/f5_tts/runtime/triton_trtllm/model_repo_f5_tts/vocoder/config.pbtxt @@ -0,0 +1,32 @@ +name: "vocoder" +backend: "tensorrt" +default_model_filename: "vocoder.plan" +max_batch_size: 4 + +input [ + { + name: "mel" + data_type: TYPE_FP32 + dims: [ 100, -1 ] + } +] + +output [ + { + name: "waveform" + data_type: TYPE_FP32 + dims: [ -1 ] + } +] + +dynamic_batching { + preferred_batch_size: [1, 2, 4] + max_queue_delay_microseconds: 1 +} + +instance_group [ + { + count: 1 + kind: KIND_GPU + } +] \ No newline at end of file diff --git a/src/f5_tts/src/f5_tts/runtime/triton_trtllm/patch/__init__.py b/src/f5_tts/src/f5_tts/runtime/triton_trtllm/patch/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..d6253be3d925b6605a1d00bb3074a8aeb356719a --- /dev/null +++ b/src/f5_tts/src/f5_tts/runtime/triton_trtllm/patch/__init__.py @@ -0,0 +1,199 @@ +# SPDX-FileCopyrightText: Copyright (c) 2022-2024 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +from .baichuan.model import BaichuanForCausalLM +from .bert.model import ( + BertForQuestionAnswering, + BertForSequenceClassification, + BertModel, + RobertaForQuestionAnswering, + RobertaForSequenceClassification, + RobertaModel, +) +from .bloom.model import BloomForCausalLM, BloomModel +from .chatglm.config import ChatGLMConfig +from .chatglm.model import ChatGLMForCausalLM, ChatGLMModel +from .cogvlm.config import CogVLMConfig +from .cogvlm.model import CogVLMForCausalLM +from .commandr.model import CohereForCausalLM +from .dbrx.config import DbrxConfig +from .dbrx.model import DbrxForCausalLM +from .deepseek_v1.model import DeepseekForCausalLM +from .deepseek_v2.model import DeepseekV2ForCausalLM +from .dit.model import DiT +from .eagle.model import EagleForCausalLM +from .enc_dec.model import DecoderModel, EncoderModel, WhisperEncoder +from .f5tts.model import F5TTS +from .falcon.config import FalconConfig +from .falcon.model import FalconForCausalLM, FalconModel +from .gemma.config import GEMMA2_ARCHITECTURE, GEMMA_ARCHITECTURE, GemmaConfig +from .gemma.model import GemmaForCausalLM +from .gpt.config import GPTConfig +from .gpt.model import GPTForCausalLM, GPTModel +from .gptj.config import GPTJConfig +from .gptj.model import GPTJForCausalLM, GPTJModel +from .gptneox.model import GPTNeoXForCausalLM, GPTNeoXModel +from .grok.model import GrokForCausalLM +from .llama.config import LLaMAConfig +from .llama.model import LLaMAForCausalLM, LLaMAModel +from .mamba.model import MambaForCausalLM +from .medusa.config import MedusaConfig +from .medusa.model import MedusaForCausalLm +from .mllama.model import MLLaMAModel +from .modeling_utils import PretrainedConfig, PretrainedModel, SpeculativeDecodingMode +from .mpt.model import MPTForCausalLM, MPTModel +from .nemotron_nas.model import DeciLMForCausalLM +from .opt.model import OPTForCausalLM, OPTModel +from .phi.model import PhiForCausalLM, PhiModel +from .phi3.model import Phi3ForCausalLM, Phi3Model +from .qwen.model import QWenForCausalLM +from .recurrentgemma.model import RecurrentGemmaForCausalLM +from .redrafter.model import ReDrafterForCausalLM + + +__all__ = [ + "BertModel", + "BertForQuestionAnswering", + "BertForSequenceClassification", + "RobertaModel", + "RobertaForQuestionAnswering", + "RobertaForSequenceClassification", + "BloomModel", + "BloomForCausalLM", + "DiT", + "DeepseekForCausalLM", + "FalconConfig", + "DeepseekV2ForCausalLM", + "FalconForCausalLM", + "FalconModel", + "GPTConfig", + "GPTModel", + "GPTForCausalLM", + "OPTForCausalLM", + "OPTModel", + "LLaMAConfig", + "LLaMAForCausalLM", + "LLaMAModel", + "MedusaConfig", + "MedusaForCausalLm", + "ReDrafterForCausalLM", + "GPTJConfig", + "GPTJModel", + "GPTJForCausalLM", + "GPTNeoXModel", + "GPTNeoXForCausalLM", + "PhiModel", + "PhiConfig", + "Phi3Model", + "Phi3Config", + "PhiForCausalLM", + "Phi3ForCausalLM", + "ChatGLMConfig", + "ChatGLMForCausalLM", + "ChatGLMModel", + "BaichuanForCausalLM", + "QWenConfigQWenForCausalLM", + "QWenModel", + "EncoderModel", + "DecoderModel", + "PretrainedConfig", + "PretrainedModel", + "WhisperEncoder", + "MambaForCausalLM", + "MambaConfig", + "MPTForCausalLM", + "MPTModel", + "SkyworkForCausalLM", + "GemmaConfig", + "GemmaForCausalLM", + "DbrxConfig", + "DbrxForCausalLM", + "RecurrentGemmaForCausalLM", + "CogVLMConfig", + "CogVLMForCausalLM", + "EagleForCausalLM", + "SpeculativeDecodingMode", + "CohereForCausalLM", + "MLLaMAModel", + "F5TTS", +] + +MODEL_MAP = { + "GPT2LMHeadModel": GPTForCausalLM, + "GPT2LMHeadCustomModel": GPTForCausalLM, + "GPTBigCodeForCausalLM": GPTForCausalLM, + "Starcoder2ForCausalLM": GPTForCausalLM, + "FuyuForCausalLM": GPTForCausalLM, + "Kosmos2ForConditionalGeneration": GPTForCausalLM, + "JAISLMHeadModel": GPTForCausalLM, + "GPTForCausalLM": GPTForCausalLM, + "NemotronForCausalLM": GPTForCausalLM, + "OPTForCausalLM": OPTForCausalLM, + "BloomForCausalLM": BloomForCausalLM, + "RWForCausalLM": FalconForCausalLM, + "FalconForCausalLM": FalconForCausalLM, + "PhiForCausalLM": PhiForCausalLM, + "Phi3ForCausalLM": Phi3ForCausalLM, + "Phi3VForCausalLM": Phi3ForCausalLM, + "Phi3SmallForCausalLM": Phi3ForCausalLM, + "PhiMoEForCausalLM": Phi3ForCausalLM, + "MambaForCausalLM": MambaForCausalLM, + "GPTNeoXForCausalLM": GPTNeoXForCausalLM, + "GPTJForCausalLM": GPTJForCausalLM, + "MPTForCausalLM": MPTForCausalLM, + "GLMModel": ChatGLMForCausalLM, + "ChatGLMModel": ChatGLMForCausalLM, + "ChatGLMForCausalLM": ChatGLMForCausalLM, + "LlamaForCausalLM": LLaMAForCausalLM, + "ExaoneForCausalLM": LLaMAForCausalLM, + "MistralForCausalLM": LLaMAForCausalLM, + "MixtralForCausalLM": LLaMAForCausalLM, + "ArcticForCausalLM": LLaMAForCausalLM, + "Grok1ModelForCausalLM": GrokForCausalLM, + "InternLMForCausalLM": LLaMAForCausalLM, + "InternLM2ForCausalLM": LLaMAForCausalLM, + "MedusaForCausalLM": MedusaForCausalLm, + "ReDrafterForCausalLM": ReDrafterForCausalLM, + "BaichuanForCausalLM": BaichuanForCausalLM, + "BaiChuanForCausalLM": BaichuanForCausalLM, + "SkyworkForCausalLM": LLaMAForCausalLM, + GEMMA_ARCHITECTURE: GemmaForCausalLM, + GEMMA2_ARCHITECTURE: GemmaForCausalLM, + "QWenLMHeadModel": QWenForCausalLM, + "QWenForCausalLM": QWenForCausalLM, + "Qwen2ForCausalLM": QWenForCausalLM, + "Qwen2MoeForCausalLM": QWenForCausalLM, + "Qwen2ForSequenceClassification": QWenForCausalLM, + "Qwen2VLForConditionalGeneration": QWenForCausalLM, + "WhisperEncoder": WhisperEncoder, + "EncoderModel": EncoderModel, + "DecoderModel": DecoderModel, + "DbrxForCausalLM": DbrxForCausalLM, + "RecurrentGemmaForCausalLM": RecurrentGemmaForCausalLM, + "CogVLMForCausalLM": CogVLMForCausalLM, + "DiT": DiT, + "DeepseekForCausalLM": DeepseekForCausalLM, + "DeciLMForCausalLM": DeciLMForCausalLM, + "DeepseekV2ForCausalLM": DeepseekV2ForCausalLM, + "EagleForCausalLM": EagleForCausalLM, + "CohereForCausalLM": CohereForCausalLM, + "MllamaForConditionalGeneration": MLLaMAModel, + "BertForQuestionAnswering": BertForQuestionAnswering, + "BertForSequenceClassification": BertForSequenceClassification, + "BertModel": BertModel, + "RobertaModel": RobertaModel, + "RobertaForQuestionAnswering": RobertaForQuestionAnswering, + "RobertaForSequenceClassification": RobertaForSequenceClassification, + "F5TTS": F5TTS, +} diff --git a/src/f5_tts/src/f5_tts/runtime/triton_trtllm/patch/f5tts/model.py b/src/f5_tts/src/f5_tts/runtime/triton_trtllm/patch/f5tts/model.py new file mode 100644 index 0000000000000000000000000000000000000000..b8e6cabbfcb96f49dc08a9bd24819794ad62ff36 --- /dev/null +++ b/src/f5_tts/src/f5_tts/runtime/triton_trtllm/patch/f5tts/model.py @@ -0,0 +1,222 @@ +from __future__ import annotations + +import os +import sys +from collections import OrderedDict + +import tensorrt as trt +from tensorrt_llm._common import default_net + +from ..._utils import str_dtype_to_trt +from ...functional import Tensor, concat +from ...layers import Linear +from ...module import Module, ModuleList +from ...plugin import current_all_reduce_helper +from ..modeling_utils import PretrainedConfig, PretrainedModel +from .modules import AdaLayerNormZero_Final, ConvPositionEmbedding, DiTBlock, TimestepEmbedding + + +current_file_path = os.path.abspath(__file__) +parent_dir = os.path.dirname(current_file_path) +sys.path.append(parent_dir) + + +class InputEmbedding(Module): + def __init__(self, mel_dim, text_dim, out_dim): + super().__init__() + self.proj = Linear(mel_dim * 2 + text_dim, out_dim) + self.conv_pos_embed = ConvPositionEmbedding(dim=out_dim) + + def forward(self, x, cond): + x = self.proj(concat([x, cond], dim=-1)) + return self.conv_pos_embed(x) + x + + +class F5TTS(PretrainedModel): + def __init__(self, config: PretrainedConfig): + super().__init__(config) + self.dtype = str_dtype_to_trt(config.dtype) + + self.time_embed = TimestepEmbedding(config.hidden_size) + self.input_embed = InputEmbedding(config.mel_dim, config.text_dim, config.hidden_size) + + self.dim = config.hidden_size + self.depth = config.num_hidden_layers + self.transformer_blocks = ModuleList( + [ + DiTBlock( + dim=self.dim, + heads=config.num_attention_heads, + dim_head=config.dim_head, + ff_mult=config.ff_mult, + dropout=config.dropout, + ) + for _ in range(self.depth) + ] + ) + + self.norm_out = AdaLayerNormZero_Final(config.hidden_size) # final modulation + self.proj_out = Linear(config.hidden_size, config.mel_dim) + + def forward( + self, + noise, # nosied input audio + cond, # masked cond audio + time, # time step + rope_cos, + rope_sin, + input_lengths, + scale=1.0, + ): + t = self.time_embed(time) + x = self.input_embed(noise, cond) + for block in self.transformer_blocks: + x = block(x, t, rope_cos=rope_cos, rope_sin=rope_sin, input_lengths=input_lengths, scale=scale) + denoise = self.proj_out(self.norm_out(x, t)) + denoise.mark_output("denoised", self.dtype) + return denoise + + def prepare_inputs(self, **kwargs): + max_batch_size = kwargs["max_batch_size"] + batch_size_range = [2, 2, max_batch_size] + mel_size = 100 + max_seq_len = 3000 + num_frames_range = [200, 2 * max_seq_len, max_seq_len * max_batch_size] + hidden_size = 512 + concat_feature_dim = mel_size + hidden_size + freq_embed_dim = 256 + head_dim = 64 + mapping = self.config.mapping + if mapping.tp_size > 1: + current_all_reduce_helper().set_workspace_tensor(mapping, 1) + if default_net().plugin_config.remove_input_padding: + noise = Tensor( + name="noise", + dtype=self.dtype, + shape=[-1, mel_size], + dim_range=OrderedDict( + [ + ("num_frames", [num_frames_range]), + ("n_mels", [mel_size]), + ] + ), + ) + cond = Tensor( + name="cond", + dtype=self.dtype, + shape=[-1, concat_feature_dim], + dim_range=OrderedDict( + [ + ("num_frames", [num_frames_range]), + ("embeded_length", [concat_feature_dim]), + ] + ), + ) + time = Tensor( + name="time", + dtype=self.dtype, + shape=[-1, freq_embed_dim], + dim_range=OrderedDict( + [ + ("num_frames", [num_frames_range]), + ("freq_dim", [freq_embed_dim]), + ] + ), + ) + rope_cos = Tensor( + name="rope_cos", + dtype=self.dtype, + shape=[-1, head_dim], + dim_range=OrderedDict( + [ + ("num_frames", [num_frames_range]), + ("head_dim", [head_dim]), + ] + ), + ) + rope_sin = Tensor( + name="rope_sin", + dtype=self.dtype, + shape=[-1, head_dim], + dim_range=OrderedDict( + [ + ("num_frames", [num_frames_range]), + ("head_dim", [head_dim]), + ] + ), + ) + + else: + noise = Tensor( + name="noise", + dtype=self.dtype, + shape=[-1, -1, mel_size], + dim_range=OrderedDict( + [ + ("batch_size", [batch_size_range]), + ("max_duratuion", [[100, max_seq_len // 2, max_seq_len]]), + ("n_mels", [mel_size]), + ] + ), + ) + cond = Tensor( + name="cond", + dtype=self.dtype, + shape=[-1, -1, concat_feature_dim], + dim_range=OrderedDict( + [ + ("batch_size", [batch_size_range]), + ("max_duratuion", [[100, max_seq_len // 2, max_seq_len]]), + ("embeded_length", [concat_feature_dim]), + ] + ), + ) + time = Tensor( + name="time", + dtype=self.dtype, + shape=[-1, freq_embed_dim], + dim_range=OrderedDict( + [ + ("batch_size", [batch_size_range]), + ("freq_dim", [freq_embed_dim]), + ] + ), + ) + rope_cos = Tensor( + name="rope_cos", + dtype=self.dtype, + shape=[-1, -1, head_dim], + dim_range=OrderedDict( + [ + ("batch_size", [batch_size_range]), + ("max_duratuion", [[100, max_seq_len // 2, max_seq_len]]), + ("head_dim", [head_dim]), + ] + ), + ) + rope_sin = Tensor( + name="rope_sin", + dtype=self.dtype, + shape=[-1, -1, head_dim], + dim_range=OrderedDict( + [ + ("batch_size", [batch_size_range]), + ("max_duratuion", [[100, max_seq_len // 2, max_seq_len]]), + ("head_dim", [head_dim]), + ] + ), + ) + input_lengths = Tensor( + name="input_lengths", + dtype=trt.int32, + shape=[-1], + dim_range=OrderedDict([("batch_size", [batch_size_range])]), + ) + return { + "noise": noise, + "cond": cond, + "time": time, + "rope_cos": rope_cos, + "rope_sin": rope_sin, + "input_lengths": input_lengths, + } diff --git a/src/f5_tts/src/f5_tts/runtime/triton_trtllm/patch/f5tts/modules.py b/src/f5_tts/src/f5_tts/runtime/triton_trtllm/patch/f5tts/modules.py new file mode 100644 index 0000000000000000000000000000000000000000..94b251273dd879935941e885243bec497e41b824 --- /dev/null +++ b/src/f5_tts/src/f5_tts/runtime/triton_trtllm/patch/f5tts/modules.py @@ -0,0 +1,412 @@ +from __future__ import annotations + +import math +from typing import Optional + +import numpy as np +import torch +import torch.nn.functional as F +from tensorrt_llm._common import default_net + +from ..._utils import str_dtype_to_trt, trt_dtype_to_np +from ...functional import ( + Tensor, + bert_attention, + cast, + chunk, + concat, + constant, + expand, + expand_dims, + expand_dims_like, + expand_mask, + gelu, + matmul, + permute, + shape, + silu, + slice, + softmax, + squeeze, + unsqueeze, + view, +) +from ...layers import ColumnLinear, Conv1d, LayerNorm, Linear, Mish, RowLinear +from ...module import Module + + +class FeedForward(Module): + def __init__(self, dim, dim_out=None, mult=4, dropout=0.0): + super().__init__() + inner_dim = int(dim * mult) + dim_out = dim_out if dim_out is not None else dim + + self.project_in = Linear(dim, inner_dim) + self.ff = Linear(inner_dim, dim_out) + + def forward(self, x): + return self.ff(gelu(self.project_in(x))) + + +class AdaLayerNormZero(Module): + def __init__(self, dim): + super().__init__() + + self.linear = Linear(dim, dim * 6) + self.norm = LayerNorm(dim, elementwise_affine=False, eps=1e-6) + + def forward(self, x, emb=None): + emb = self.linear(silu(emb)) + shift_msa, scale_msa, gate_msa, shift_mlp, scale_mlp, gate_mlp = chunk(emb, 6, dim=1) + x = self.norm(x) + ones = constant(np.ones(1, dtype=np.float32)).cast(x.dtype) + if default_net().plugin_config.remove_input_padding: + x = x * (ones + scale_msa) + shift_msa + else: + x = x * (ones + unsqueeze(scale_msa, 1)) + unsqueeze(shift_msa, 1) + return x, gate_msa, shift_mlp, scale_mlp, gate_mlp + + +class AdaLayerNormZero_Final(Module): + def __init__(self, dim): + super().__init__() + + self.linear = Linear(dim, dim * 2) + + self.norm = LayerNorm(dim, elementwise_affine=False, eps=1e-6) + + def forward(self, x, emb): + emb = self.linear(silu(emb)) + scale, shift = chunk(emb, 2, dim=1) + ones = constant(np.ones(1, dtype=np.float32)).cast(x.dtype) + if default_net().plugin_config.remove_input_padding: + x = self.norm(x) * (ones + scale) + shift + else: + x = self.norm(x) * unsqueeze((ones + scale), 1) + x = x + unsqueeze(shift, 1) + return x + + +class ConvPositionEmbedding(Module): + def __init__(self, dim, kernel_size=31, groups=16): + super().__init__() + assert kernel_size % 2 != 0 + self.conv1d1 = Conv1d(dim, dim, kernel_size, groups=groups, padding=kernel_size // 2) + self.conv1d2 = Conv1d(dim, dim, kernel_size, groups=groups, padding=kernel_size // 2) + self.mish = Mish() + + def forward(self, x, mask=None): # noqa: F722 + if default_net().plugin_config.remove_input_padding: + x = unsqueeze(x, 0) + x = permute(x, [0, 2, 1]) + x = self.mish(self.conv1d2(self.mish(self.conv1d1(x)))) + out = permute(x, [0, 2, 1]) + if default_net().plugin_config.remove_input_padding: + out = squeeze(out, 0) + return out + + +class Attention(Module): + def __init__( + self, + processor: AttnProcessor, + dim: int, + heads: int = 16, + dim_head: int = 64, + dropout: float = 0.0, + context_dim: Optional[int] = None, # if not None -> joint attention + context_pre_only=None, + ): + super().__init__() + + if not hasattr(F, "scaled_dot_product_attention"): + raise ImportError("Attention equires PyTorch 2.0, to use it, please upgrade PyTorch to 2.0.") + + self.processor = processor + + self.dim = dim # hidden_size + self.heads = heads + self.inner_dim = dim_head * heads + self.dropout = dropout + self.attention_head_size = dim_head + self.context_dim = context_dim + self.context_pre_only = context_pre_only + self.tp_size = 1 + self.num_attention_heads = heads // self.tp_size + self.num_attention_kv_heads = heads // self.tp_size # 8 + self.dtype = str_dtype_to_trt("float32") + self.attention_hidden_size = self.attention_head_size * self.num_attention_heads + self.to_q = ColumnLinear( + dim, + self.tp_size * self.num_attention_heads * self.attention_head_size, + bias=True, + dtype=self.dtype, + tp_group=None, + tp_size=self.tp_size, + ) + self.to_k = ColumnLinear( + dim, + self.tp_size * self.num_attention_heads * self.attention_head_size, + bias=True, + dtype=self.dtype, + tp_group=None, + tp_size=self.tp_size, + ) + self.to_v = ColumnLinear( + dim, + self.tp_size * self.num_attention_heads * self.attention_head_size, + bias=True, + dtype=self.dtype, + tp_group=None, + tp_size=self.tp_size, + ) + + if self.context_dim is not None: + self.to_k_c = Linear(context_dim, self.inner_dim) + self.to_v_c = Linear(context_dim, self.inner_dim) + if self.context_pre_only is not None: + self.to_q_c = Linear(context_dim, self.inner_dim) + + self.to_out = RowLinear( + self.tp_size * self.num_attention_heads * self.attention_head_size, + dim, + bias=True, + dtype=self.dtype, + tp_group=None, + tp_size=self.tp_size, + ) + + if self.context_pre_only is not None and not self.context_pre_only: + self.to_out_c = Linear(self.inner_dim, dim) + + def forward( + self, + x, # noised input x + rope_cos, + rope_sin, + input_lengths, + c=None, # context c + scale=1.0, + rope=None, + c_rope=None, # rotary position embedding for c + ) -> torch.Tensor: + if c is not None: + return self.processor(self, x, c=c, input_lengths=input_lengths, scale=scale, rope=rope, c_rope=c_rope) + else: + return self.processor( + self, x, rope_cos=rope_cos, rope_sin=rope_sin, input_lengths=input_lengths, scale=scale + ) + + +def rotate_every_two_3dim(tensor: Tensor) -> Tensor: + shape_tensor = concat( + [shape(tensor, i) / 2 if i == (tensor.ndim() - 1) else shape(tensor, i) for i in range(tensor.ndim())] + ) + if default_net().plugin_config.remove_input_padding: + assert tensor.ndim() == 2 + x1 = slice(tensor, [0, 0], shape_tensor, [1, 2]) + x2 = slice(tensor, [0, 1], shape_tensor, [1, 2]) + x1 = expand_dims(x1, 2) + x2 = expand_dims(x2, 2) + zero = constant(np.ascontiguousarray(np.zeros([1], dtype=trt_dtype_to_np(tensor.dtype)))) + x2 = zero - x2 + x = concat([x2, x1], 2) + out = view(x, concat([shape(x, 0), shape(x, 1) * 2])) + else: + assert tensor.ndim() == 3 + + x1 = slice(tensor, [0, 0, 0], shape_tensor, [1, 1, 2]) + x2 = slice(tensor, [0, 0, 1], shape_tensor, [1, 1, 2]) + x1 = expand_dims(x1, 3) + x2 = expand_dims(x2, 3) + zero = constant(np.ascontiguousarray(np.zeros([1], dtype=trt_dtype_to_np(tensor.dtype)))) + x2 = zero - x2 + x = concat([x2, x1], 3) + out = view(x, concat([shape(x, 0), shape(x, 1), shape(x, 2) * 2])) + + return out + + +def apply_rotary_pos_emb_3dim(x, rope_cos, rope_sin): + if default_net().plugin_config.remove_input_padding: + rot_dim = shape(rope_cos, -1) # 64 + new_t_shape = concat([shape(x, 0), rot_dim]) # (-1, 64) + x_ = slice(x, [0, 0], new_t_shape, [1, 1]) + end_dim = shape(x, -1) - shape(rope_cos, -1) + new_t_unrotated_shape = concat([shape(x, 0), end_dim]) # (2, -1, 960) + x_unrotated = slice(x, concat([0, rot_dim]), new_t_unrotated_shape, [1, 1]) + out = concat([x_ * rope_cos + rotate_every_two_3dim(x_) * rope_sin, x_unrotated], dim=-1) + else: + rot_dim = shape(rope_cos, 2) # 64 + new_t_shape = concat([shape(x, 0), shape(x, 1), rot_dim]) # (2, -1, 64) + x_ = slice(x, [0, 0, 0], new_t_shape, [1, 1, 1]) + end_dim = shape(x, 2) - shape(rope_cos, 2) + new_t_unrotated_shape = concat([shape(x, 0), shape(x, 1), end_dim]) # (2, -1, 960) + x_unrotated = slice(x, concat([0, 0, rot_dim]), new_t_unrotated_shape, [1, 1, 1]) + out = concat([x_ * rope_cos + rotate_every_two_3dim(x_) * rope_sin, x_unrotated], dim=-1) + return out + + +class AttnProcessor: + def __init__(self): + pass + + def __call__( + self, + attn, + x, # noised input x + rope_cos, + rope_sin, + input_lengths, + scale=1.0, + rope=None, + ) -> torch.FloatTensor: + query = attn.to_q(x) + key = attn.to_k(x) + value = attn.to_v(x) + # k,v,q all (2,1226,1024) + query = apply_rotary_pos_emb_3dim(query, rope_cos, rope_sin) + key = apply_rotary_pos_emb_3dim(key, rope_cos, rope_sin) + + # attention + inner_dim = key.shape[-1] + norm_factor = math.sqrt(attn.attention_head_size) + q_scaling = 1.0 / norm_factor + mask = None + if not default_net().plugin_config.remove_input_padding: + N = shape(x, 1) + B = shape(x, 0) + seq_len_2d = concat([1, N]) + max_position_embeddings = 4096 + # create position ids + position_ids_buffer = constant(np.expand_dims(np.arange(max_position_embeddings).astype(np.int32), 0)) + tmp_position_ids = slice(position_ids_buffer, starts=[0, 0], sizes=seq_len_2d) + tmp_position_ids = expand(tmp_position_ids, concat([B, N])) # BxL + tmp_input_lengths = unsqueeze(input_lengths, 1) # Bx1 + tmp_input_lengths = expand(tmp_input_lengths, concat([B, N])) # BxL + mask = tmp_position_ids < tmp_input_lengths # BxL + mask = mask.cast("int32") + + if default_net().plugin_config.bert_attention_plugin: + qkv = concat([query, key, value], dim=-1) + # TRT plugin mode + assert input_lengths is not None + if default_net().plugin_config.remove_input_padding: + qkv = qkv.view(concat([-1, 3 * inner_dim])) + max_input_length = constant( + np.zeros( + [ + 2048, + ], + dtype=np.int32, + ) + ) + else: + max_input_length = None + context = bert_attention( + qkv, + input_lengths, + attn.num_attention_heads, + attn.attention_head_size, + q_scaling=q_scaling, + max_input_length=max_input_length, + ) + else: + assert not default_net().plugin_config.remove_input_padding + + def transpose_for_scores(x): + new_x_shape = concat([shape(x, 0), shape(x, 1), attn.num_attention_heads, attn.attention_head_size]) + + y = x.view(new_x_shape) + y = y.transpose(1, 2) + return y + + def transpose_for_scores_k(x): + new_x_shape = concat([shape(x, 0), shape(x, 1), attn.num_attention_heads, attn.attention_head_size]) + + y = x.view(new_x_shape) + y = y.permute([0, 2, 3, 1]) + return y + + query = transpose_for_scores(query) + key = transpose_for_scores_k(key) + value = transpose_for_scores(value) + + attention_scores = matmul(query, key, use_fp32_acc=False) + + if mask is not None: + attention_mask = expand_mask(mask, shape(query, 2)) + attention_mask = cast(attention_mask, attention_scores.dtype) + attention_scores = attention_scores + attention_mask + + attention_probs = softmax(attention_scores, dim=-1) + + context = matmul(attention_probs, value, use_fp32_acc=False).transpose(1, 2) + context = context.view(concat([shape(context, 0), shape(context, 1), attn.attention_hidden_size])) + context = attn.to_out(context) + if mask is not None: + mask = mask.view(concat([shape(mask, 0), shape(mask, 1), 1])) + mask = expand_dims_like(mask, context) + mask = cast(mask, context.dtype) + context = context * mask + return context + + +# DiT Block +class DiTBlock(Module): + def __init__(self, dim, heads, dim_head, ff_mult=2, dropout=0.1): + super().__init__() + + self.attn_norm = AdaLayerNormZero(dim) + self.attn = Attention( + processor=AttnProcessor(), + dim=dim, + heads=heads, + dim_head=dim_head, + dropout=dropout, + ) + + self.ff_norm = LayerNorm(dim, elementwise_affine=False, eps=1e-6) + self.ff = FeedForward(dim=dim, mult=ff_mult, dropout=dropout) + + def forward( + self, x, t, rope_cos, rope_sin, input_lengths, scale=1.0, rope=ModuleNotFoundError + ): # x: noised input, t: time embedding + # pre-norm & modulation for attention input + norm, gate_msa, shift_mlp, scale_mlp, gate_mlp = self.attn_norm(x, emb=t) + # attention + # norm ----> (2,1226,1024) + attn_output = self.attn(x=norm, rope_cos=rope_cos, rope_sin=rope_sin, input_lengths=input_lengths, scale=scale) + + # process attention output for input x + if default_net().plugin_config.remove_input_padding: + x = x + gate_msa * attn_output + else: + x = x + unsqueeze(gate_msa, 1) * attn_output + ones = constant(np.ones(1, dtype=np.float32)).cast(x.dtype) + if default_net().plugin_config.remove_input_padding: + norm = self.ff_norm(x) * (ones + scale_mlp) + shift_mlp + else: + norm = self.ff_norm(x) * (ones + unsqueeze(scale_mlp, 1)) + unsqueeze(shift_mlp, 1) + # norm = self.ff_norm(x) * (ones + scale_mlp) + shift_mlp + ff_output = self.ff(norm) + if default_net().plugin_config.remove_input_padding: + x = x + gate_mlp * ff_output + else: + x = x + unsqueeze(gate_mlp, 1) * ff_output + + return x + + +class TimestepEmbedding(Module): + def __init__(self, dim, freq_embed_dim=256, dtype=None): + super().__init__() + # self.time_embed = SinusPositionEmbedding(freq_embed_dim) + self.mlp1 = Linear(freq_embed_dim, dim, bias=True, dtype=dtype) + self.mlp2 = Linear(dim, dim, bias=True, dtype=dtype) + + def forward(self, timestep): + t_freq = self.mlp1(timestep) + t_freq = silu(t_freq) + t_emb = self.mlp2(t_freq) + return t_emb diff --git a/src/f5_tts/src/f5_tts/runtime/triton_trtllm/requirements-pytorch.txt b/src/f5_tts/src/f5_tts/runtime/triton_trtllm/requirements-pytorch.txt new file mode 100644 index 0000000000000000000000000000000000000000..5a3cdf7d932f7f44cac3dcd79e89c1dbfaf0b19b --- /dev/null +++ b/src/f5_tts/src/f5_tts/runtime/triton_trtllm/requirements-pytorch.txt @@ -0,0 +1,24 @@ +accelerate>=0.33.0 +bitsandbytes>0.37.0 +cached_path +click +datasets +ema_pytorch>=0.5.2 +gradio>=3.45.2 +hydra-core>=1.3.0 +jieba +librosa +matplotlib +numpy<=1.26.4 +pydub +pypinyin +safetensors +soundfile +tomli +torch>=2.0.0 +# torchaudio>=2.0.0 +torchdiffeq +tqdm>=4.65.0 +transformers +x_transformers>=1.31.14 +packaging>=24.2 \ No newline at end of file diff --git a/src/f5_tts/src/f5_tts/runtime/triton_trtllm/run.sh b/src/f5_tts/src/f5_tts/runtime/triton_trtllm/run.sh new file mode 100644 index 0000000000000000000000000000000000000000..6a71fb1ededc69fe776cdf93150c6c0d868dfe19 --- /dev/null +++ b/src/f5_tts/src/f5_tts/runtime/triton_trtllm/run.sh @@ -0,0 +1,110 @@ +stage=$1 +stop_stage=$2 +model=$3 # F5TTS_Base +if [ -z "$model" ]; then + echo "Model is none, using default model F5TTS_Base" + model=F5TTS_Base +fi +echo "Start stage: $stage, Stop stage: $stop_stage, Model: $model" +export CUDA_VISIBLE_DEVICES=0 + +F5_TTS_HF_DOWNLOAD_PATH=./F5-TTS +F5_TTS_TRT_LLM_CHECKPOINT_PATH=./trtllm_ckpt +F5_TTS_TRT_LLM_ENGINE_PATH=./f5_trt_llm_engine + +vocoder_trt_engine_path=vocos_vocoder.plan +model_repo=./model_repo + +if [ $stage -le 0 ] && [ $stop_stage -ge 0 ]; then + echo "Downloading f5 tts from huggingface" + huggingface-cli download SWivid/F5-TTS --local-dir $F5_TTS_HF_DOWNLOAD_PATH + +fi + +if [ $stage -le 1 ] && [ $stop_stage -ge 1 ]; then + echo "Converting checkpoint" + python3 ./scripts/convert_checkpoint.py \ + --timm_ckpt "$F5_TTS_HF_DOWNLOAD_PATH/$model/model_1200000.pt" \ + --output_dir "$F5_TTS_TRT_LLM_CHECKPOINT_PATH" --model_name $model + python_package_path=/usr/local/lib/python3.12/dist-packages + cp -r patch/* $python_package_path/tensorrt_llm/models + trtllm-build --checkpoint_dir $F5_TTS_TRT_LLM_CHECKPOINT_PATH \ + --max_batch_size 8 \ + --output_dir $F5_TTS_TRT_LLM_ENGINE_PATH --remove_input_padding disable +fi + +if [ $stage -le 2 ] && [ $stop_stage -ge 2 ]; then + echo "Exporting vocos vocoder" + onnx_vocoder_path=vocos_vocoder.onnx + python3 scripts/export_vocoder_to_onnx.py --vocoder vocos --output-path $onnx_vocoder_path + bash scripts/export_vocos_trt.sh $onnx_vocoder_path $vocoder_trt_engine_path +fi + +if [ $stage -le 3 ] && [ $stop_stage -ge 3 ]; then + echo "Building triton server" + rm -r $model_repo + cp -r ./model_repo_f5_tts $model_repo + python3 scripts/fill_template.py -i $model_repo/f5_tts/config.pbtxt vocab:$F5_TTS_HF_DOWNLOAD_PATH/$model/vocab.txt,model:$F5_TTS_HF_DOWNLOAD_PATH/$model/model_1200000.pt,trtllm:$F5_TTS_TRT_LLM_ENGINE_PATH,vocoder:vocos + cp $vocoder_trt_engine_path $model_repo/vocoder/1/vocoder.plan +fi + +if [ $stage -le 4 ] && [ $stop_stage -ge 4 ]; then + echo "Starting triton server" + tritonserver --model-repository=$model_repo +fi + +if [ $stage -le 5 ] && [ $stop_stage -ge 5 ]; then + echo "Testing triton server" + num_task=1 + log_dir=./log_concurrent_tasks_${num_task} + rm -r $log_dir + python3 client_grpc.py --num-tasks $num_task --huggingface-dataset yuekai/seed_tts --split-name wenetspeech4tts --log-dir $log_dir +fi + +if [ $stage -le 6 ] && [ $stop_stage -ge 6 ]; then + echo "Testing http client" + audio=../../infer/examples/basic/basic_ref_en.wav + reference_text="Some call me nature, others call me mother nature." + target_text="I don't really care what you call me. I've been a silent spectator, watching species evolve, empires rise and fall. But always remember, I am mighty and enduring." + python3 client_http.py --reference-audio $audio --reference-text "$reference_text" --target-text "$target_text" +fi + +if [ $stage -le 7 ] && [ $stop_stage -ge 7 ]; then + echo "TRT-LLM: offline decoding benchmark test" + batch_size=1 + split_name=wenetspeech4tts + backend_type=trt + log_dir=./log_benchmark_batch_size_${batch_size}_${split_name}_${backend_type} + rm -r $log_dir + ln -s model_repo_f5_tts/f5_tts/1/f5_tts_trtllm.py ./ + torchrun --nproc_per_node=1 \ + benchmark.py --output-dir $log_dir \ + --batch-size $batch_size \ + --enable-warmup \ + --split-name $split_name \ + --model-path $F5_TTS_HF_DOWNLOAD_PATH/$model/model_1200000.pt \ + --vocab-file $F5_TTS_HF_DOWNLOAD_PATH/$model/vocab.txt \ + --vocoder-trt-engine-path $vocoder_trt_engine_path \ + --backend-type $backend_type \ + --tllm-model-dir $F5_TTS_TRT_LLM_ENGINE_PATH || exit 1 +fi + +if [ $stage -le 8 ] && [ $stop_stage -ge 8 ]; then + echo "Native Pytorch: offline decoding benchmark test" + pip install -r requirements-pytorch.txt + batch_size=1 + split_name=wenetspeech4tts + backend_type=pytorch + log_dir=./log_benchmark_batch_size_${batch_size}_${split_name}_${backend_type} + rm -r $log_dir + ln -s model_repo_f5_tts/f5_tts/1/f5_tts_trtllm.py ./ + torchrun --nproc_per_node=1 \ + benchmark.py --output-dir $log_dir \ + --batch-size $batch_size \ + --split-name $split_name \ + --enable-warmup \ + --model-path $F5_TTS_HF_DOWNLOAD_PATH/$model/model_1200000.pt \ + --vocab-file $F5_TTS_HF_DOWNLOAD_PATH/$model/vocab.txt \ + --backend-type $backend_type \ + --tllm-model-dir $F5_TTS_TRT_LLM_ENGINE_PATH || exit 1 +fi \ No newline at end of file diff --git a/src/f5_tts/src/f5_tts/runtime/triton_trtllm/scripts/conv_stft.py b/src/f5_tts/src/f5_tts/runtime/triton_trtllm/scripts/conv_stft.py new file mode 100644 index 0000000000000000000000000000000000000000..80f8feec61331cfe62632606386aa09c92ae6ef5 --- /dev/null +++ b/src/f5_tts/src/f5_tts/runtime/triton_trtllm/scripts/conv_stft.py @@ -0,0 +1,248 @@ +# Modified from https://github.com/echocatzh/conv-stft/blob/master/conv_stft/conv_stft.py + +# Copyright (c) 2024, NVIDIA CORPORATION. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# MIT License + +# Copyright (c) 2020 Shimin Zhang + +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: + +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. + +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +import torch as th +import torch.nn.functional as F +from scipy.signal import check_COLA, get_window + + +support_clp_op = None +if th.__version__ >= "1.7.0": + from torch.fft import rfft as fft + + support_clp_op = True +else: + from torch import rfft as fft + + +class STFT(th.nn.Module): + def __init__( + self, + win_len=1024, + win_hop=512, + fft_len=1024, + enframe_mode="continue", + win_type="hann", + win_sqrt=False, + pad_center=True, + ): + """ + Implement of STFT using 1D convolution and 1D transpose convolutions. + Implement of framing the signal in 2 ways, `break` and `continue`. + `break` method is a kaldi-like framing. + `continue` method is a librosa-like framing. + + More information about `perfect reconstruction`: + 1. https://ww2.mathworks.cn/help/signal/ref/stft.html + 2. https://docs.scipy.org/doc/scipy/reference/generated/scipy.signal.get_window.html + + Args: + win_len (int): Number of points in one frame. Defaults to 1024. + win_hop (int): Number of framing stride. Defaults to 512. + fft_len (int): Number of DFT points. Defaults to 1024. + enframe_mode (str, optional): `break` and `continue`. Defaults to 'continue'. + win_type (str, optional): The type of window to create. Defaults to 'hann'. + win_sqrt (bool, optional): using square root window. Defaults to True. + pad_center (bool, optional): `perfect reconstruction` opts. Defaults to True. + """ + super(STFT, self).__init__() + assert enframe_mode in ["break", "continue"] + assert fft_len >= win_len + self.win_len = win_len + self.win_hop = win_hop + self.fft_len = fft_len + self.mode = enframe_mode + self.win_type = win_type + self.win_sqrt = win_sqrt + self.pad_center = pad_center + self.pad_amount = self.fft_len // 2 + + en_k, fft_k, ifft_k, ola_k = self.__init_kernel__() + self.register_buffer("en_k", en_k) + self.register_buffer("fft_k", fft_k) + self.register_buffer("ifft_k", ifft_k) + self.register_buffer("ola_k", ola_k) + + def __init_kernel__(self): + """ + Generate enframe_kernel, fft_kernel, ifft_kernel and overlap-add kernel. + ** enframe_kernel: Using conv1d layer and identity matrix. + ** fft_kernel: Using linear layer for matrix multiplication. In fact, + enframe_kernel and fft_kernel can be combined, But for the sake of + readability, I took the two apart. + ** ifft_kernel, pinv of fft_kernel. + ** overlap-add kernel, just like enframe_kernel, but transposed. + + Returns: + tuple: four kernels. + """ + enframed_kernel = th.eye(self.fft_len)[:, None, :] + if support_clp_op: + tmp = fft(th.eye(self.fft_len)) + fft_kernel = th.stack([tmp.real, tmp.imag], dim=2) + else: + fft_kernel = fft(th.eye(self.fft_len), 1) + if self.mode == "break": + enframed_kernel = th.eye(self.win_len)[:, None, :] + fft_kernel = fft_kernel[: self.win_len] + fft_kernel = th.cat((fft_kernel[:, :, 0], fft_kernel[:, :, 1]), dim=1) + ifft_kernel = th.pinverse(fft_kernel)[:, None, :] + window = get_window(self.win_type, self.win_len) + + self.perfect_reconstruct = check_COLA(window, self.win_len, self.win_len - self.win_hop) + window = th.FloatTensor(window) + if self.mode == "continue": + left_pad = (self.fft_len - self.win_len) // 2 + right_pad = left_pad + (self.fft_len - self.win_len) % 2 + window = F.pad(window, (left_pad, right_pad)) + if self.win_sqrt: + self.padded_window = window + window = th.sqrt(window) + else: + self.padded_window = window**2 + + fft_kernel = fft_kernel.T * window + ifft_kernel = ifft_kernel * window + ola_kernel = th.eye(self.fft_len)[: self.win_len, None, :] + if self.mode == "continue": + ola_kernel = th.eye(self.fft_len)[:, None, : self.fft_len] + return enframed_kernel, fft_kernel, ifft_kernel, ola_kernel + + def is_perfect(self): + """ + Whether the parameters win_len, win_hop and win_sqrt + obey constants overlap-add(COLA) + + Returns: + bool: Return true if parameters obey COLA. + """ + return self.perfect_reconstruct and self.pad_center + + def transform(self, inputs, return_type="complex"): + """Take input data (audio) to STFT domain. + + Args: + inputs (tensor): Tensor of floats, with shape (num_batch, num_samples) + return_type (str, optional): return (mag, phase) when `magphase`, + return (real, imag) when `realimag` and complex(real, imag) when `complex`. + Defaults to 'complex'. + + Returns: + tuple: (mag, phase) when `magphase`, return (real, imag) when + `realimag`. Defaults to 'complex', each elements with shape + [num_batch, num_frequencies, num_frames] + """ + assert return_type in ["magphase", "realimag", "complex"] + if inputs.dim() == 2: + inputs = th.unsqueeze(inputs, 1) + self.num_samples = inputs.size(-1) + if self.pad_center: + inputs = F.pad(inputs, (self.pad_amount, self.pad_amount), mode="reflect") + enframe_inputs = F.conv1d(inputs, self.en_k, stride=self.win_hop) + outputs = th.transpose(enframe_inputs, 1, 2) + outputs = F.linear(outputs, self.fft_k) + outputs = th.transpose(outputs, 1, 2) + dim = self.fft_len // 2 + 1 + real = outputs[:, :dim, :] + imag = outputs[:, dim:, :] + if return_type == "realimag": + return real, imag + elif return_type == "complex": + assert support_clp_op + return th.complex(real, imag) + else: + mags = th.sqrt(real**2 + imag**2) + phase = th.atan2(imag, real) + return mags, phase + + def inverse(self, input1, input2=None, input_type="magphase"): + """Call the inverse STFT (iSTFT), given tensors produced + by the `transform` function. + + Args: + input1 (tensors): Magnitude/Real-part of STFT with shape + [num_batch, num_frequencies, num_frames] + input2 (tensors): Phase/Imag-part of STFT with shape + [num_batch, num_frequencies, num_frames] + input_type (str, optional): Mathematical meaning of input tensor's. + Defaults to 'magphase'. + + Returns: + tensors: Reconstructed audio given magnitude and phase. Of + shape [num_batch, num_samples] + """ + assert input_type in ["magphase", "realimag"] + if input_type == "realimag": + real, imag = None, None + if support_clp_op and th.is_complex(input1): + real, imag = input1.real, input1.imag + else: + real, imag = input1, input2 + else: + real = input1 * th.cos(input2) + imag = input1 * th.sin(input2) + inputs = th.cat([real, imag], dim=1) + outputs = F.conv_transpose1d(inputs, self.ifft_k, stride=self.win_hop) + t = (self.padded_window[None, :, None]).repeat(1, 1, inputs.size(-1)) + t = t.to(inputs.device) + coff = F.conv_transpose1d(t, self.ola_k, stride=self.win_hop) + + num_frames = input1.size(-1) + num_samples = num_frames * self.win_hop + + rm_start, rm_end = self.pad_amount, self.pad_amount + num_samples + + outputs = outputs[..., rm_start:rm_end] + coff = coff[..., rm_start:rm_end] + coffidx = th.where(coff > 1e-8) + outputs[coffidx] = outputs[coffidx] / (coff[coffidx]) + return outputs.squeeze(dim=1) + + def forward(self, inputs): + """Take input data (audio) to STFT domain and then back to audio. + + Args: + inputs (tensor): Tensor of floats, with shape [num_batch, num_samples] + + Returns: + tensor: Reconstructed audio given magnitude and phase. + Of shape [num_batch, num_samples] + """ + mag, phase = self.transform(inputs) + rec_wav = self.inverse(mag, phase) + return rec_wav diff --git a/src/f5_tts/src/f5_tts/runtime/triton_trtllm/scripts/convert_checkpoint.py b/src/f5_tts/src/f5_tts/runtime/triton_trtllm/scripts/convert_checkpoint.py new file mode 100644 index 0000000000000000000000000000000000000000..15a80e59ecc639412d90ed792bdb0aec55c98d1b --- /dev/null +++ b/src/f5_tts/src/f5_tts/runtime/triton_trtllm/scripts/convert_checkpoint.py @@ -0,0 +1,358 @@ +import argparse +import json +import os +import re +import time +import traceback +from concurrent.futures import ThreadPoolExecutor, as_completed + +import safetensors.torch +import torch +from tensorrt_llm import str_dtype_to_torch +from tensorrt_llm.mapping import Mapping +from tensorrt_llm.models.convert_utils import split, split_matrix_tp + + +def split_q_tp(v, n_head, n_hidden, tensor_parallel, rank): + split_v = split(v, tensor_parallel, rank, dim=1) + return split_v.contiguous() + + +def split_q_bias_tp(v, n_head, n_hidden, tensor_parallel, rank): + split_v = split(v, tensor_parallel, rank, dim=0) + return split_v.contiguous() + + +FACEBOOK_DIT_NAME_MAPPING = { + "^time_embed.time_mlp.0.weight$": "time_embed.mlp1.weight", + "^time_embed.time_mlp.0.bias$": "time_embed.mlp1.bias", + "^time_embed.time_mlp.2.weight$": "time_embed.mlp2.weight", + "^time_embed.time_mlp.2.bias$": "time_embed.mlp2.bias", + "^input_embed.conv_pos_embed.conv1d.0.weight$": "input_embed.conv_pos_embed.conv1d1.weight", + "^input_embed.conv_pos_embed.conv1d.0.bias$": "input_embed.conv_pos_embed.conv1d1.bias", + "^input_embed.conv_pos_embed.conv1d.2.weight$": "input_embed.conv_pos_embed.conv1d2.weight", + "^input_embed.conv_pos_embed.conv1d.2.bias$": "input_embed.conv_pos_embed.conv1d2.bias", + "^transformer_blocks.0.attn.to_out.0.weight$": "transformer_blocks.0.attn.to_out.weight", + "^transformer_blocks.0.attn.to_out.0.bias$": "transformer_blocks.0.attn.to_out.bias", + "^transformer_blocks.1.attn.to_out.0.weight$": "transformer_blocks.1.attn.to_out.weight", + "^transformer_blocks.1.attn.to_out.0.bias$": "transformer_blocks.1.attn.to_out.bias", + "^transformer_blocks.2.attn.to_out.0.weight$": "transformer_blocks.2.attn.to_out.weight", + "^transformer_blocks.2.attn.to_out.0.bias$": "transformer_blocks.2.attn.to_out.bias", + "^transformer_blocks.3.attn.to_out.0.weight$": "transformer_blocks.3.attn.to_out.weight", + "^transformer_blocks.3.attn.to_out.0.bias$": "transformer_blocks.3.attn.to_out.bias", + "^transformer_blocks.4.attn.to_out.0.weight$": "transformer_blocks.4.attn.to_out.weight", + "^transformer_blocks.4.attn.to_out.0.bias$": "transformer_blocks.4.attn.to_out.bias", + "^transformer_blocks.5.attn.to_out.0.weight$": "transformer_blocks.5.attn.to_out.weight", + "^transformer_blocks.5.attn.to_out.0.bias$": "transformer_blocks.5.attn.to_out.bias", + "^transformer_blocks.6.attn.to_out.0.weight$": "transformer_blocks.6.attn.to_out.weight", + "^transformer_blocks.6.attn.to_out.0.bias$": "transformer_blocks.6.attn.to_out.bias", + "^transformer_blocks.7.attn.to_out.0.weight$": "transformer_blocks.7.attn.to_out.weight", + "^transformer_blocks.7.attn.to_out.0.bias$": "transformer_blocks.7.attn.to_out.bias", + "^transformer_blocks.8.attn.to_out.0.weight$": "transformer_blocks.8.attn.to_out.weight", + "^transformer_blocks.8.attn.to_out.0.bias$": "transformer_blocks.8.attn.to_out.bias", + "^transformer_blocks.9.attn.to_out.0.weight$": "transformer_blocks.9.attn.to_out.weight", + "^transformer_blocks.9.attn.to_out.0.bias$": "transformer_blocks.9.attn.to_out.bias", + "^transformer_blocks.10.attn.to_out.0.weight$": "transformer_blocks.10.attn.to_out.weight", + "^transformer_blocks.10.attn.to_out.0.bias$": "transformer_blocks.10.attn.to_out.bias", + "^transformer_blocks.11.attn.to_out.0.weight$": "transformer_blocks.11.attn.to_out.weight", + "^transformer_blocks.11.attn.to_out.0.bias$": "transformer_blocks.11.attn.to_out.bias", + "^transformer_blocks.12.attn.to_out.0.weight$": "transformer_blocks.12.attn.to_out.weight", + "^transformer_blocks.12.attn.to_out.0.bias$": "transformer_blocks.12.attn.to_out.bias", + "^transformer_blocks.13.attn.to_out.0.weight$": "transformer_blocks.13.attn.to_out.weight", + "^transformer_blocks.13.attn.to_out.0.bias$": "transformer_blocks.13.attn.to_out.bias", + "^transformer_blocks.14.attn.to_out.0.weight$": "transformer_blocks.14.attn.to_out.weight", + "^transformer_blocks.14.attn.to_out.0.bias$": "transformer_blocks.14.attn.to_out.bias", + "^transformer_blocks.15.attn.to_out.0.weight$": "transformer_blocks.15.attn.to_out.weight", + "^transformer_blocks.15.attn.to_out.0.bias$": "transformer_blocks.15.attn.to_out.bias", + "^transformer_blocks.16.attn.to_out.0.weight$": "transformer_blocks.16.attn.to_out.weight", + "^transformer_blocks.16.attn.to_out.0.bias$": "transformer_blocks.16.attn.to_out.bias", + "^transformer_blocks.17.attn.to_out.0.weight$": "transformer_blocks.17.attn.to_out.weight", + "^transformer_blocks.17.attn.to_out.0.bias$": "transformer_blocks.17.attn.to_out.bias", + "^transformer_blocks.18.attn.to_out.0.weight$": "transformer_blocks.18.attn.to_out.weight", + "^transformer_blocks.18.attn.to_out.0.bias$": "transformer_blocks.18.attn.to_out.bias", + "^transformer_blocks.19.attn.to_out.0.weight$": "transformer_blocks.19.attn.to_out.weight", + "^transformer_blocks.19.attn.to_out.0.bias$": "transformer_blocks.19.attn.to_out.bias", + "^transformer_blocks.20.attn.to_out.0.weight$": "transformer_blocks.20.attn.to_out.weight", + "^transformer_blocks.20.attn.to_out.0.bias$": "transformer_blocks.20.attn.to_out.bias", + "^transformer_blocks.21.attn.to_out.0.weight$": "transformer_blocks.21.attn.to_out.weight", + "^transformer_blocks.21.attn.to_out.0.bias$": "transformer_blocks.21.attn.to_out.bias", + "^transformer_blocks.0.ff.ff.0.0.weight$": "transformer_blocks.0.ff.project_in.weight", + "^transformer_blocks.0.ff.ff.0.0.bias$": "transformer_blocks.0.ff.project_in.bias", + "^transformer_blocks.0.ff.ff.2.weight$": "transformer_blocks.0.ff.ff.weight", + "^transformer_blocks.0.ff.ff.2.bias$": "transformer_blocks.0.ff.ff.bias", + "^transformer_blocks.1.ff.ff.0.0.weight$": "transformer_blocks.1.ff.project_in.weight", + "^transformer_blocks.1.ff.ff.0.0.bias$": "transformer_blocks.1.ff.project_in.bias", + "^transformer_blocks.1.ff.ff.2.weight$": "transformer_blocks.1.ff.ff.weight", + "^transformer_blocks.1.ff.ff.2.bias$": "transformer_blocks.1.ff.ff.bias", + "^transformer_blocks.2.ff.ff.0.0.weight$": "transformer_blocks.2.ff.project_in.weight", + "^transformer_blocks.2.ff.ff.0.0.bias$": "transformer_blocks.2.ff.project_in.bias", + "^transformer_blocks.2.ff.ff.2.weight$": "transformer_blocks.2.ff.ff.weight", + "^transformer_blocks.2.ff.ff.2.bias$": "transformer_blocks.2.ff.ff.bias", + "^transformer_blocks.3.ff.ff.0.0.weight$": "transformer_blocks.3.ff.project_in.weight", + "^transformer_blocks.3.ff.ff.0.0.bias$": "transformer_blocks.3.ff.project_in.bias", + "^transformer_blocks.3.ff.ff.2.weight$": "transformer_blocks.3.ff.ff.weight", + "^transformer_blocks.3.ff.ff.2.bias$": "transformer_blocks.3.ff.ff.bias", + "^transformer_blocks.4.ff.ff.0.0.weight$": "transformer_blocks.4.ff.project_in.weight", + "^transformer_blocks.4.ff.ff.0.0.bias$": "transformer_blocks.4.ff.project_in.bias", + "^transformer_blocks.4.ff.ff.2.weight$": "transformer_blocks.4.ff.ff.weight", + "^transformer_blocks.4.ff.ff.2.bias$": "transformer_blocks.4.ff.ff.bias", + "^transformer_blocks.5.ff.ff.0.0.weight$": "transformer_blocks.5.ff.project_in.weight", + "^transformer_blocks.5.ff.ff.0.0.bias$": "transformer_blocks.5.ff.project_in.bias", + "^transformer_blocks.5.ff.ff.2.weight$": "transformer_blocks.5.ff.ff.weight", + "^transformer_blocks.5.ff.ff.2.bias$": "transformer_blocks.5.ff.ff.bias", + "^transformer_blocks.6.ff.ff.0.0.weight$": "transformer_blocks.6.ff.project_in.weight", + "^transformer_blocks.6.ff.ff.0.0.bias$": "transformer_blocks.6.ff.project_in.bias", + "^transformer_blocks.6.ff.ff.2.weight$": "transformer_blocks.6.ff.ff.weight", + "^transformer_blocks.6.ff.ff.2.bias$": "transformer_blocks.6.ff.ff.bias", + "^transformer_blocks.7.ff.ff.0.0.weight$": "transformer_blocks.7.ff.project_in.weight", + "^transformer_blocks.7.ff.ff.0.0.bias$": "transformer_blocks.7.ff.project_in.bias", + "^transformer_blocks.7.ff.ff.2.weight$": "transformer_blocks.7.ff.ff.weight", + "^transformer_blocks.7.ff.ff.2.bias$": "transformer_blocks.7.ff.ff.bias", + "^transformer_blocks.8.ff.ff.0.0.weight$": "transformer_blocks.8.ff.project_in.weight", + "^transformer_blocks.8.ff.ff.0.0.bias$": "transformer_blocks.8.ff.project_in.bias", + "^transformer_blocks.8.ff.ff.2.weight$": "transformer_blocks.8.ff.ff.weight", + "^transformer_blocks.8.ff.ff.2.bias$": "transformer_blocks.8.ff.ff.bias", + "^transformer_blocks.9.ff.ff.0.0.weight$": "transformer_blocks.9.ff.project_in.weight", + "^transformer_blocks.9.ff.ff.0.0.bias$": "transformer_blocks.9.ff.project_in.bias", + "^transformer_blocks.9.ff.ff.2.weight$": "transformer_blocks.9.ff.ff.weight", + "^transformer_blocks.9.ff.ff.2.bias$": "transformer_blocks.9.ff.ff.bias", + "^transformer_blocks.10.ff.ff.0.0.weight$": "transformer_blocks.10.ff.project_in.weight", + "^transformer_blocks.10.ff.ff.0.0.bias$": "transformer_blocks.10.ff.project_in.bias", + "^transformer_blocks.10.ff.ff.2.weight$": "transformer_blocks.10.ff.ff.weight", + "^transformer_blocks.10.ff.ff.2.bias$": "transformer_blocks.10.ff.ff.bias", + "^transformer_blocks.11.ff.ff.0.0.weight$": "transformer_blocks.11.ff.project_in.weight", + "^transformer_blocks.11.ff.ff.0.0.bias$": "transformer_blocks.11.ff.project_in.bias", + "^transformer_blocks.11.ff.ff.2.weight$": "transformer_blocks.11.ff.ff.weight", + "^transformer_blocks.11.ff.ff.2.bias$": "transformer_blocks.11.ff.ff.bias", + "^transformer_blocks.12.ff.ff.0.0.weight$": "transformer_blocks.12.ff.project_in.weight", + "^transformer_blocks.12.ff.ff.0.0.bias$": "transformer_blocks.12.ff.project_in.bias", + "^transformer_blocks.12.ff.ff.2.weight$": "transformer_blocks.12.ff.ff.weight", + "^transformer_blocks.12.ff.ff.2.bias$": "transformer_blocks.12.ff.ff.bias", + "^transformer_blocks.13.ff.ff.0.0.weight$": "transformer_blocks.13.ff.project_in.weight", + "^transformer_blocks.13.ff.ff.0.0.bias$": "transformer_blocks.13.ff.project_in.bias", + "^transformer_blocks.13.ff.ff.2.weight$": "transformer_blocks.13.ff.ff.weight", + "^transformer_blocks.13.ff.ff.2.bias$": "transformer_blocks.13.ff.ff.bias", + "^transformer_blocks.14.ff.ff.0.0.weight$": "transformer_blocks.14.ff.project_in.weight", + "^transformer_blocks.14.ff.ff.0.0.bias$": "transformer_blocks.14.ff.project_in.bias", + "^transformer_blocks.14.ff.ff.2.weight$": "transformer_blocks.14.ff.ff.weight", + "^transformer_blocks.14.ff.ff.2.bias$": "transformer_blocks.14.ff.ff.bias", + "^transformer_blocks.15.ff.ff.0.0.weight$": "transformer_blocks.15.ff.project_in.weight", + "^transformer_blocks.15.ff.ff.0.0.bias$": "transformer_blocks.15.ff.project_in.bias", + "^transformer_blocks.15.ff.ff.2.weight$": "transformer_blocks.15.ff.ff.weight", + "^transformer_blocks.15.ff.ff.2.bias$": "transformer_blocks.15.ff.ff.bias", + "^transformer_blocks.16.ff.ff.0.0.weight$": "transformer_blocks.16.ff.project_in.weight", + "^transformer_blocks.16.ff.ff.0.0.bias$": "transformer_blocks.16.ff.project_in.bias", + "^transformer_blocks.16.ff.ff.2.weight$": "transformer_blocks.16.ff.ff.weight", + "^transformer_blocks.16.ff.ff.2.bias$": "transformer_blocks.16.ff.ff.bias", + "^transformer_blocks.17.ff.ff.0.0.weight$": "transformer_blocks.17.ff.project_in.weight", + "^transformer_blocks.17.ff.ff.0.0.bias$": "transformer_blocks.17.ff.project_in.bias", + "^transformer_blocks.17.ff.ff.2.weight$": "transformer_blocks.17.ff.ff.weight", + "^transformer_blocks.17.ff.ff.2.bias$": "transformer_blocks.17.ff.ff.bias", + "^transformer_blocks.18.ff.ff.0.0.weight$": "transformer_blocks.18.ff.project_in.weight", + "^transformer_blocks.18.ff.ff.0.0.bias$": "transformer_blocks.18.ff.project_in.bias", + "^transformer_blocks.18.ff.ff.2.weight$": "transformer_blocks.18.ff.ff.weight", + "^transformer_blocks.18.ff.ff.2.bias$": "transformer_blocks.18.ff.ff.bias", + "^transformer_blocks.19.ff.ff.0.0.weight$": "transformer_blocks.19.ff.project_in.weight", + "^transformer_blocks.19.ff.ff.0.0.bias$": "transformer_blocks.19.ff.project_in.bias", + "^transformer_blocks.19.ff.ff.2.weight$": "transformer_blocks.19.ff.ff.weight", + "^transformer_blocks.19.ff.ff.2.bias$": "transformer_blocks.19.ff.ff.bias", + "^transformer_blocks.20.ff.ff.0.0.weight$": "transformer_blocks.20.ff.project_in.weight", + "^transformer_blocks.20.ff.ff.0.0.bias$": "transformer_blocks.20.ff.project_in.bias", + "^transformer_blocks.20.ff.ff.2.weight$": "transformer_blocks.20.ff.ff.weight", + "^transformer_blocks.20.ff.ff.2.bias$": "transformer_blocks.20.ff.ff.bias", + "^transformer_blocks.21.ff.ff.0.0.weight$": "transformer_blocks.21.ff.project_in.weight", + "^transformer_blocks.21.ff.ff.0.0.bias$": "transformer_blocks.21.ff.project_in.bias", + "^transformer_blocks.21.ff.ff.2.weight$": "transformer_blocks.21.ff.ff.weight", + "^transformer_blocks.21.ff.ff.2.bias$": "transformer_blocks.21.ff.ff.bias", +} + + +def parse_arguments(): + parser = argparse.ArgumentParser() + parser.add_argument( + "--model_name", + type=str, + default="F5TTS_Base", + choices=[ + "F5TTS_Base", + ], + ) # TODO: support F5TTS_v1_Base + parser.add_argument("--timm_ckpt", type=str, default="./ckpts/model_1200000.pt") + parser.add_argument( + "--output_dir", type=str, default="./tllm_checkpoint", help="The path to save the TensorRT-LLM checkpoint" + ) + parser.add_argument("--hidden_size", type=int, default=1024, help="The hidden size of DiT") + parser.add_argument("--depth", type=int, default=22, help="The number of DiTBlock layers") + parser.add_argument("--num_heads", type=int, default=16, help="The number of heads of attention module") + parser.add_argument("--cfg_scale", type=float, default=4.0) + parser.add_argument("--tp_size", type=int, default=1, help="N-way tensor parallelism size") + parser.add_argument("--cp_size", type=int, default=1, help="Context parallelism size") + parser.add_argument("--pp_size", type=int, default=1, help="N-way pipeline parallelism size") + parser.add_argument("--dtype", type=str, default="float16", choices=["float32", "bfloat16", "float16"]) + parser.add_argument("--fp8_linear", action="store_true", help="Whether use FP8 for linear layers") + parser.add_argument( + "--workers", type=int, default=1, help="The number of workers for converting checkpoint in parallel" + ) + args = parser.parse_args() + return args + + +def convert_timm_dit(args, mapping, dtype="float32"): + weights = {} + tik = time.time() + torch_dtype = str_dtype_to_torch(dtype) + tensor_parallel = mapping.tp_size + + model_params = dict(torch.load(args.timm_ckpt)) + model_params = { + k: v for k, v in model_params["ema_model_state_dict"].items() if k.startswith("ema_model.transformer") + } + prefix = "ema_model.transformer." + model_params = {key[len(prefix) :] if key.startswith(prefix) else key: value for key, value in model_params.items()} + + timm_to_trtllm_name = FACEBOOK_DIT_NAME_MAPPING + + def get_trtllm_name(timm_name): + for k, v in timm_to_trtllm_name.items(): + m = re.match(k, timm_name) + if m is not None: + if "*" in v: + v = v.replace("*", m.groups()[0]) + return v + return timm_name + + weights = dict() + for name, param in model_params.items(): + if name == "input_embed.conv_pos_embed.conv1d.0.weight" or name == "input_embed.conv_pos_embed.conv1d.2.weight": + weights[get_trtllm_name(name)] = param.contiguous().to(torch_dtype).unsqueeze(-1) + else: + weights[get_trtllm_name(name)] = param.contiguous().to(torch_dtype) + + assert len(weights) == len(model_params) + + # new_prefix = 'f5_transformer.' + new_prefix = "" + weights = {new_prefix + key: value for key, value in weights.items()} + import math + + scale_factor = math.pow(64, -0.25) + for k, v in weights.items(): + if re.match("^transformer_blocks.*.attn.to_k.weight$", k): + weights[k] *= scale_factor + weights[k] = split_q_tp(v, args.num_heads, args.hidden_size, tensor_parallel, mapping.tp_rank) + + elif re.match("^transformer_blocks.*.attn.to_k.bias$", k): + weights[k] *= scale_factor + weights[k] = split_q_bias_tp(v, args.num_heads, args.hidden_size, tensor_parallel, mapping.tp_rank) + + elif re.match("^transformer_blocks.*.attn.to_q.weight$", k): + weights[k] = split_q_tp(v, args.num_heads, args.hidden_size, tensor_parallel, mapping.tp_rank) + weights[k] *= scale_factor + + elif re.match("^transformer_blocks.*.attn.to_q.bias$", k): + weights[k] = split_q_bias_tp(v, args.num_heads, args.hidden_size, tensor_parallel, mapping.tp_rank) + weights[k] *= scale_factor + + elif re.match("^transformer_blocks.*.attn.to_v.weight$", k): + weights[k] = split_q_tp(v, args.num_heads, args.hidden_size, tensor_parallel, mapping.tp_rank) + + elif re.match("^transformer_blocks.*.attn.to_v.bias$", k): + weights[k] = split_q_bias_tp(v, args.num_heads, args.hidden_size, tensor_parallel, mapping.tp_rank) + + elif re.match("^transformer_blocks.*.attn.to_out.weight$", k): + weights[k] = split_matrix_tp(v, tensor_parallel, mapping.tp_rank, dim=1) + + tok = time.time() + t = time.strftime("%H:%M:%S", time.gmtime(tok - tik)) + print(f"Weights loaded. Total time: {t}") + return weights + + +def save_config(args): + if not os.path.exists(args.output_dir): + os.makedirs(args.output_dir) + config = { + "architecture": "F5TTS", + "dtype": args.dtype, + "hidden_size": 1024, + "num_hidden_layers": 22, + "num_attention_heads": 16, + "dim_head": 64, + "dropout": 0.1, + "ff_mult": 2, + "mel_dim": 100, + "text_num_embeds": 256, + "text_dim": 512, + "conv_layers": 4, + "long_skip_connection": False, + "mapping": { + "world_size": args.cp_size * args.tp_size * args.pp_size, + "cp_size": args.cp_size, + "tp_size": args.tp_size, + "pp_size": args.pp_size, + }, + } + if args.fp8_linear: + config["quantization"] = { + "quant_algo": "FP8", + # TODO: add support for exclude modules. + # 'exclude_modules': "*final_layer*", + } + + with open(os.path.join(args.output_dir, "config.json"), "w") as f: + json.dump(config, f, indent=4) + + +def covert_and_save(args, rank): + if rank == 0: + save_config(args) + + mapping = Mapping( + world_size=args.cp_size * args.tp_size * args.pp_size, + rank=rank, + cp_size=args.cp_size, + tp_size=args.tp_size, + pp_size=args.pp_size, + ) + + weights = convert_timm_dit(args, mapping, dtype=args.dtype) + + safetensors.torch.save_file(weights, os.path.join(args.output_dir, f"rank{rank}.safetensors")) + + +def execute(workers, func, args): + if workers == 1: + for rank, f in enumerate(func): + f(args, rank) + else: + with ThreadPoolExecutor(max_workers=workers) as p: + futures = [p.submit(f, args, rank) for rank, f in enumerate(func)] + exceptions = [] + for future in as_completed(futures): + try: + future.result() + except Exception as e: + traceback.print_exc() + exceptions.append(e) + assert len(exceptions) == 0, "Checkpoint conversion failed, please check error log." + + +def main(): + args = parse_arguments() + world_size = args.cp_size * args.tp_size * args.pp_size + + assert args.pp_size == 1, "PP is not supported yet." + + tik = time.time() + if args.timm_ckpt is None: + return + print("start execute") + execute(args.workers, [covert_and_save] * world_size, args) + + tok = time.time() + t = time.strftime("%H:%M:%S", time.gmtime(tok - tik)) + print(f"Total time of converting checkpoints: {t}") + + +if __name__ == "__main__": + main() diff --git a/src/f5_tts/src/f5_tts/runtime/triton_trtllm/scripts/export_vocoder_to_onnx.py b/src/f5_tts/src/f5_tts/runtime/triton_trtllm/scripts/export_vocoder_to_onnx.py new file mode 100644 index 0000000000000000000000000000000000000000..0190a893bc59c3255127264ea087a4fd3b129ebd --- /dev/null +++ b/src/f5_tts/src/f5_tts/runtime/triton_trtllm/scripts/export_vocoder_to_onnx.py @@ -0,0 +1,138 @@ +# Copyright (c) 2024, NVIDIA CORPORATION. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import argparse + +import torch +import torch.nn as nn +from conv_stft import STFT +from huggingface_hub import hf_hub_download +from vocos import Vocos + + +opset_version = 17 + + +def get_args(): + parser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter) + parser.add_argument( + "--vocoder", + type=str, + default="vocos", + choices=["vocos", "bigvgan"], + help="Vocoder to export", + ) + parser.add_argument( + "--output-path", + type=str, + default="./vocos_vocoder.onnx", + help="Output path", + ) + return parser.parse_args() + + +class ISTFTHead(nn.Module): + def __init__(self, n_fft: int, hop_length: int): + super().__init__() + self.out = None + self.stft = STFT(fft_len=n_fft, win_hop=hop_length, win_len=n_fft) + + def forward(self, x: torch.Tensor): + x = self.out(x).transpose(1, 2) + mag, p = x.chunk(2, dim=1) + mag = torch.exp(mag) + mag = torch.clip(mag, max=1e2) + real = mag * torch.cos(p) + imag = mag * torch.sin(p) + audio = self.stft.inverse(input1=real, input2=imag, input_type="realimag") + return audio + + +class VocosVocoder(nn.Module): + def __init__(self, vocos_vocoder): + super(VocosVocoder, self).__init__() + self.vocos_vocoder = vocos_vocoder + istft_head_out = self.vocos_vocoder.head.out + n_fft = self.vocos_vocoder.head.istft.n_fft + hop_length = self.vocos_vocoder.head.istft.hop_length + istft_head_for_export = ISTFTHead(n_fft, hop_length) + istft_head_for_export.out = istft_head_out + self.vocos_vocoder.head = istft_head_for_export + + def forward(self, mel): + waveform = self.vocos_vocoder.decode(mel) + return waveform + + +def export_VocosVocoder(vocos_vocoder, output_path, verbose): + vocos_vocoder = VocosVocoder(vocos_vocoder).cuda() + vocos_vocoder.eval() + + dummy_batch_size = 8 + dummy_input_length = 500 + + dummy_mel = torch.randn(dummy_batch_size, 100, dummy_input_length).cuda() + + with torch.no_grad(): + dummy_waveform = vocos_vocoder(mel=dummy_mel) + print(dummy_waveform.shape) + + dummy_input = dummy_mel + + torch.onnx.export( + vocos_vocoder, + dummy_input, + output_path, + opset_version=opset_version, + do_constant_folding=True, + input_names=["mel"], + output_names=["waveform"], + dynamic_axes={ + "mel": {0: "batch_size", 2: "input_length"}, + "waveform": {0: "batch_size", 1: "output_length"}, + }, + verbose=verbose, + ) + + print("Exported to {}".format(output_path)) + + +def load_vocoder(vocoder_name="vocos", is_local=False, local_path="", device="cpu", hf_cache_dir=None): + if vocoder_name == "vocos": + # vocoder = Vocos.from_pretrained("charactr/vocos-mel-24khz").to(device) + if is_local: + print(f"Load vocos from local path {local_path}") + config_path = f"{local_path}/config.yaml" + model_path = f"{local_path}/pytorch_model.bin" + else: + print("Download Vocos from huggingface charactr/vocos-mel-24khz") + repo_id = "charactr/vocos-mel-24khz" + config_path = hf_hub_download(repo_id=repo_id, cache_dir=hf_cache_dir, filename="config.yaml") + model_path = hf_hub_download(repo_id=repo_id, cache_dir=hf_cache_dir, filename="pytorch_model.bin") + vocoder = Vocos.from_hparams(config_path) + state_dict = torch.load(model_path, map_location="cpu", weights_only=True) + vocoder.load_state_dict(state_dict) + vocoder = vocoder.eval().to(device) + elif vocoder_name == "bigvgan": + raise NotImplementedError("BigVGAN is not supported yet") + vocoder.remove_weight_norm() + vocoder = vocoder.eval().to(device) + return vocoder + + +if __name__ == "__main__": + args = get_args() + vocoder = load_vocoder(vocoder_name=args.vocoder, device="cpu", hf_cache_dir=None) + if args.vocoder == "vocos": + export_VocosVocoder(vocoder, args.output_path, verbose=False) diff --git a/src/f5_tts/src/f5_tts/runtime/triton_trtllm/scripts/export_vocos_trt.sh b/src/f5_tts/src/f5_tts/runtime/triton_trtllm/scripts/export_vocos_trt.sh new file mode 100644 index 0000000000000000000000000000000000000000..28b2a9212b041e36d0a04039a0a9ed5b297926e6 --- /dev/null +++ b/src/f5_tts/src/f5_tts/runtime/triton_trtllm/scripts/export_vocos_trt.sh @@ -0,0 +1,43 @@ +#!/bin/bash +# Copyright (c) 2025, NVIDIA CORPORATION. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +TRTEXEC="/usr/src/tensorrt/bin/trtexec" + +ONNX_PATH=$1 +ENGINE_PATH=$2 +echo "ONNX_PATH: $ONNX_PATH" +echo "ENGINE_PATH: $ENGINE_PATH" +PRECISION="fp32" + + +MIN_BATCH_SIZE=1 +OPT_BATCH_SIZE=1 +MAX_BATCH_SIZE=8 + +MIN_INPUT_LENGTH=1 +OPT_INPUT_LENGTH=1000 +MAX_INPUT_LENGTH=3000 + +MEL_MIN_SHAPE="${MIN_BATCH_SIZE}x100x${MIN_INPUT_LENGTH}" +MEL_OPT_SHAPE="${OPT_BATCH_SIZE}x100x${OPT_INPUT_LENGTH}" +MEL_MAX_SHAPE="${MAX_BATCH_SIZE}x100x${MAX_INPUT_LENGTH}" + +${TRTEXEC} \ + --minShapes="mel:${MEL_MIN_SHAPE}" \ + --optShapes="mel:${MEL_OPT_SHAPE}" \ + --maxShapes="mel:${MEL_MAX_SHAPE}" \ + --onnx=${ONNX_PATH} \ + --saveEngine=${ENGINE_PATH} + diff --git a/src/f5_tts/src/f5_tts/runtime/triton_trtllm/scripts/fill_template.py b/src/f5_tts/src/f5_tts/runtime/triton_trtllm/scripts/fill_template.py new file mode 100644 index 0000000000000000000000000000000000000000..608597889460d2776ac86aec46b8552cb3967555 --- /dev/null +++ b/src/f5_tts/src/f5_tts/runtime/triton_trtllm/scripts/fill_template.py @@ -0,0 +1,36 @@ +#! /usr/bin/env python3 +from argparse import ArgumentParser +from string import Template + + +def main(file_path, substitutions, in_place, participant_ids): + with open(file_path) as f: + pbtxt = Template(f.read()) + + sub_dict = {"max_queue_size": 0} + sub_dict["participant_ids"] = participant_ids + for sub in substitutions.split(","): + key, value = sub.split(":") + sub_dict[key] = value + + pbtxt = pbtxt.safe_substitute(sub_dict) + + if in_place: + with open(file_path, "w") as f: + f.write(pbtxt) + else: + print(pbtxt) + + +if __name__ == "__main__": + parser = ArgumentParser() + parser.add_argument("file_path", help="path of the .pbtxt to modify") + parser.add_argument( + "substitutions", + help="substitutions to perform, in the format variable_name_1:value_1,variable_name_2:value_2...", + ) + parser.add_argument("--in_place", "-i", action="store_true", help="do the operation in-place") + parser.add_argument("--participant_ids", help="Participant IDs for the model", default="") + args = parser.parse_args() + + main(**vars(args)) diff --git a/src/f5_tts/src/f5_tts/scripts/count_max_epoch.py b/src/f5_tts/src/f5_tts/scripts/count_max_epoch.py new file mode 100644 index 0000000000000000000000000000000000000000..08922b9da5ba130db945769a50862dfdd75d92ca --- /dev/null +++ b/src/f5_tts/src/f5_tts/scripts/count_max_epoch.py @@ -0,0 +1,33 @@ +"""ADAPTIVE BATCH SIZE""" + +print("Adaptive batch size: using grouping batch sampler, frames_per_gpu fixed fed in") +print(" -> least padding, gather wavs with accumulated frames in a batch\n") + +# data +total_hours = 95282 +mel_hop_length = 256 +mel_sampling_rate = 24000 + +# target +wanted_max_updates = 1200000 + +# train params +gpus = 8 +frames_per_gpu = 38400 # 8 * 38400 = 307200 +grad_accum = 1 + +# intermediate +mini_batch_frames = frames_per_gpu * grad_accum * gpus +mini_batch_hours = mini_batch_frames * mel_hop_length / mel_sampling_rate / 3600 +updates_per_epoch = total_hours / mini_batch_hours +# steps_per_epoch = updates_per_epoch * grad_accum + +# result +epochs = wanted_max_updates / updates_per_epoch +print(f"epochs should be set to: {epochs:.0f} ({epochs / grad_accum:.1f} x gd_acum {grad_accum})") +print(f"progress_bar should show approx. 0/{updates_per_epoch:.0f} updates") +# print(f" or approx. 0/{steps_per_epoch:.0f} steps") + +# others +print(f"total {total_hours:.0f} hours") +print(f"mini-batch of {mini_batch_frames:.0f} frames, {mini_batch_hours:.2f} hours per mini-batch") diff --git a/src/f5_tts/src/f5_tts/scripts/count_params_gflops.py b/src/f5_tts/src/f5_tts/scripts/count_params_gflops.py new file mode 100644 index 0000000000000000000000000000000000000000..3f89bc6f28725bb0cdc3118478f802b9d5cd7c4c --- /dev/null +++ b/src/f5_tts/src/f5_tts/scripts/count_params_gflops.py @@ -0,0 +1,40 @@ +import os +import sys + + +sys.path.append(os.getcwd()) + +import thop +import torch + +from f5_tts.model import CFM, DiT + + +""" ~155M """ +# transformer = UNetT(dim = 768, depth = 20, heads = 12, ff_mult = 4) +# transformer = UNetT(dim = 768, depth = 20, heads = 12, ff_mult = 4, text_dim = 512, conv_layers = 4) +# transformer = DiT(dim = 768, depth = 18, heads = 12, ff_mult = 2) +# transformer = DiT(dim = 768, depth = 18, heads = 12, ff_mult = 2, text_dim = 512, conv_layers = 4) +# transformer = DiT(dim = 768, depth = 18, heads = 12, ff_mult = 2, text_dim = 512, conv_layers = 4, long_skip_connection = True) +# transformer = MMDiT(dim = 512, depth = 16, heads = 16, ff_mult = 2) + +""" ~335M """ +# FLOPs: 622.1 G, Params: 333.2 M +# transformer = UNetT(dim = 1024, depth = 24, heads = 16, ff_mult = 4) +# FLOPs: 363.4 G, Params: 335.8 M +transformer = DiT(dim=1024, depth=22, heads=16, ff_mult=2, text_dim=512, conv_layers=4) + + +model = CFM(transformer=transformer) +target_sample_rate = 24000 +n_mel_channels = 100 +hop_length = 256 +duration = 20 +frame_length = int(duration * target_sample_rate / hop_length) +text_length = 150 + +flops, params = thop.profile( + model, inputs=(torch.randn(1, frame_length, n_mel_channels), torch.zeros(1, text_length, dtype=torch.long)) +) +print(f"FLOPs: {flops / 1e9} G") +print(f"Params: {params / 1e6} M") diff --git a/src/f5_tts/src/f5_tts/socket_client.py b/src/f5_tts/src/f5_tts/socket_client.py new file mode 100644 index 0000000000000000000000000000000000000000..f41117dc4e596d4dc5ab8872dadc9c1d12c03fa8 --- /dev/null +++ b/src/f5_tts/src/f5_tts/socket_client.py @@ -0,0 +1,63 @@ +import asyncio +import logging +import socket +import time + +import numpy as np +import pyaudio + + +logging.basicConfig(level=logging.INFO) +logger = logging.getLogger(__name__) + + +async def listen_to_F5TTS(text, server_ip="localhost", server_port=9998): + client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + await asyncio.get_event_loop().run_in_executor(None, client_socket.connect, (server_ip, int(server_port))) + + start_time = time.time() + first_chunk_time = None + + async def play_audio_stream(): + nonlocal first_chunk_time + p = pyaudio.PyAudio() + stream = p.open(format=pyaudio.paFloat32, channels=1, rate=24000, output=True, frames_per_buffer=2048) + + try: + while True: + data = await asyncio.get_event_loop().run_in_executor(None, client_socket.recv, 8192) + if not data: + break + if data == b"END": + logger.info("End of audio received.") + break + + audio_array = np.frombuffer(data, dtype=np.float32) + stream.write(audio_array.tobytes()) + + if first_chunk_time is None: + first_chunk_time = time.time() + + finally: + stream.stop_stream() + stream.close() + p.terminate() + + logger.info(f"Total time taken: {time.time() - start_time:.4f} seconds") + + try: + data_to_send = f"{text}".encode("utf-8") + await asyncio.get_event_loop().run_in_executor(None, client_socket.sendall, data_to_send) + await play_audio_stream() + + except Exception as e: + logger.error(f"Error in listen_to_F5TTS: {e}") + + finally: + client_socket.close() + + +if __name__ == "__main__": + text_to_send = "As a Reader assistant, I'm familiar with new technology. which are key to its improved performance in terms of both training speed and inference efficiency. Let's break down the components" + + asyncio.run(listen_to_F5TTS(text_to_send)) diff --git a/src/f5_tts/src/f5_tts/socket_server.py b/src/f5_tts/src/f5_tts/socket_server.py new file mode 100644 index 0000000000000000000000000000000000000000..1b09e6639f0c1a938f12c6b82876c5480bd6b107 --- /dev/null +++ b/src/f5_tts/src/f5_tts/socket_server.py @@ -0,0 +1,268 @@ +import argparse +import gc +import logging +import queue +import socket +import struct +import threading +import traceback +import wave +from importlib.resources import files + +import numpy as np +import torch +import torchaudio +from huggingface_hub import hf_hub_download +from hydra.utils import get_class +from omegaconf import OmegaConf + +from f5_tts.infer.utils_infer import ( + chunk_text, + infer_batch_process, + load_model, + load_vocoder, + preprocess_ref_audio_text, +) + + +logging.basicConfig(level=logging.INFO) +logger = logging.getLogger(__name__) + + +class AudioFileWriterThread(threading.Thread): + """Threaded file writer to avoid blocking the TTS streaming process.""" + + def __init__(self, output_file, sampling_rate): + super().__init__() + self.output_file = output_file + self.sampling_rate = sampling_rate + self.queue = queue.Queue() + self.stop_event = threading.Event() + self.audio_data = [] + + def run(self): + """Process queued audio data and write it to a file.""" + logger.info("AudioFileWriterThread started.") + with wave.open(self.output_file, "wb") as wf: + wf.setnchannels(1) + wf.setsampwidth(2) + wf.setframerate(self.sampling_rate) + + while not self.stop_event.is_set() or not self.queue.empty(): + try: + chunk = self.queue.get(timeout=0.1) + if chunk is not None: + chunk = np.int16(chunk * 32767) + self.audio_data.append(chunk) + wf.writeframes(chunk.tobytes()) + except queue.Empty: + continue + + def add_chunk(self, chunk): + """Add a new chunk to the queue.""" + self.queue.put(chunk) + + def stop(self): + """Stop writing and ensure all queued data is written.""" + self.stop_event.set() + self.join() + logger.info("Audio writing completed.") + + +class TTSStreamingProcessor: + def __init__(self, model, ckpt_file, vocab_file, ref_audio, ref_text, device=None, dtype=torch.float32): + self.device = device or ( + "cuda" + if torch.cuda.is_available() + else "xpu" + if torch.xpu.is_available() + else "mps" + if torch.backends.mps.is_available() + else "cpu" + ) + model_cfg = OmegaConf.load(str(files("f5_tts").joinpath(f"configs/{model}.yaml"))) + self.model_cls = get_class(f"f5_tts.model.{model_cfg.model.backbone}") + self.model_arc = model_cfg.model.arch + self.mel_spec_type = model_cfg.model.mel_spec.mel_spec_type + self.sampling_rate = model_cfg.model.mel_spec.target_sample_rate + + self.model = self.load_ema_model(ckpt_file, vocab_file, dtype) + self.vocoder = self.load_vocoder_model() + + self.update_reference(ref_audio, ref_text) + self._warm_up() + self.file_writer_thread = None + self.first_package = True + + def load_ema_model(self, ckpt_file, vocab_file, dtype): + return load_model( + self.model_cls, + self.model_arc, + ckpt_path=ckpt_file, + mel_spec_type=self.mel_spec_type, + vocab_file=vocab_file, + ode_method="euler", + use_ema=True, + device=self.device, + ).to(self.device, dtype=dtype) + + def load_vocoder_model(self): + return load_vocoder(vocoder_name=self.mel_spec_type, is_local=False, local_path=None, device=self.device) + + def update_reference(self, ref_audio, ref_text): + self.ref_audio, self.ref_text = preprocess_ref_audio_text(ref_audio, ref_text) + self.audio, self.sr = torchaudio.load(self.ref_audio) + + ref_audio_duration = self.audio.shape[-1] / self.sr + ref_text_byte_len = len(self.ref_text.encode("utf-8")) + self.max_chars = int(ref_text_byte_len / (ref_audio_duration) * (25 - ref_audio_duration)) + self.few_chars = int(ref_text_byte_len / (ref_audio_duration) * (25 - ref_audio_duration) / 2) + self.min_chars = int(ref_text_byte_len / (ref_audio_duration) * (25 - ref_audio_duration) / 4) + + def _warm_up(self): + logger.info("Warming up the model...") + gen_text = "Warm-up text for the model." + for _ in infer_batch_process( + (self.audio, self.sr), + self.ref_text, + [gen_text], + self.model, + self.vocoder, + progress=None, + device=self.device, + streaming=True, + ): + pass + logger.info("Warm-up completed.") + + def generate_stream(self, text, conn): + text_batches = chunk_text(text, max_chars=self.max_chars) + if self.first_package: + text_batches = chunk_text(text_batches[0], max_chars=self.few_chars) + text_batches[1:] + text_batches = chunk_text(text_batches[0], max_chars=self.min_chars) + text_batches[1:] + self.first_package = False + + audio_stream = infer_batch_process( + (self.audio, self.sr), + self.ref_text, + text_batches, + self.model, + self.vocoder, + progress=None, + device=self.device, + streaming=True, + chunk_size=2048, + ) + + # Reset the file writer thread + if self.file_writer_thread is not None: + self.file_writer_thread.stop() + self.file_writer_thread = AudioFileWriterThread("output.wav", self.sampling_rate) + self.file_writer_thread.start() + + for audio_chunk, _ in audio_stream: + if len(audio_chunk) > 0: + logger.info(f"Generated audio chunk of size: {len(audio_chunk)}") + + # Send audio chunk via socket + conn.sendall(struct.pack(f"{len(audio_chunk)}f", *audio_chunk)) + + # Write to file asynchronously + self.file_writer_thread.add_chunk(audio_chunk) + + logger.info("Finished sending audio stream.") + conn.sendall(b"END") # Send end signal + + # Ensure all audio data is written before exiting + self.file_writer_thread.stop() + + +def handle_client(conn, processor): + try: + with conn: + conn.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) + while True: + data = conn.recv(1024) + if not data: + processor.first_package = True + break + data_str = data.decode("utf-8").strip() + logger.info(f"Received text: {data_str}") + + try: + processor.generate_stream(data_str, conn) + except Exception as inner_e: + logger.error(f"Error during processing: {inner_e}") + traceback.print_exc() + break + except Exception as e: + logger.error(f"Error handling client: {e}") + traceback.print_exc() + + +def start_server(host, port, processor): + with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: + s.bind((host, port)) + s.listen() + logger.info(f"Server started on {host}:{port}") + while True: + conn, addr = s.accept() + logger.info(f"Connected by {addr}") + handle_client(conn, processor) + + +if __name__ == "__main__": + parser = argparse.ArgumentParser() + + parser.add_argument("--host", default="0.0.0.0") + parser.add_argument("--port", default=9998) + + parser.add_argument( + "--model", + default="F5TTS_v1_Base", + help="The model name, e.g. F5TTS_v1_Base", + ) + parser.add_argument( + "--ckpt_file", + default=str(hf_hub_download(repo_id="SWivid/F5-TTS", filename="F5TTS_v1_Base/model_1250000.safetensors")), + help="Path to the model checkpoint file", + ) + parser.add_argument( + "--vocab_file", + default="", + help="Path to the vocab file if customized", + ) + + parser.add_argument( + "--ref_audio", + default=str(files("f5_tts").joinpath("infer/examples/basic/basic_ref_en.wav")), + help="Reference audio to provide model with speaker characteristics", + ) + parser.add_argument( + "--ref_text", + default="", + help="Reference audio subtitle, leave empty to auto-transcribe", + ) + + parser.add_argument("--device", default=None, help="Device to run the model on") + parser.add_argument("--dtype", default=torch.float32, help="Data type to use for model inference") + + args = parser.parse_args() + + try: + # Initialize the processor with the model and vocoder + processor = TTSStreamingProcessor( + model=args.model, + ckpt_file=args.ckpt_file, + vocab_file=args.vocab_file, + ref_audio=args.ref_audio, + ref_text=args.ref_text, + device=args.device, + dtype=args.dtype, + ) + + # Start the server + start_server(args.host, args.port, processor) + + except KeyboardInterrupt: + gc.collect() diff --git a/src/f5_tts/src/f5_tts/train/README.md b/src/f5_tts/src/f5_tts/train/README.md new file mode 100644 index 0000000000000000000000000000000000000000..488d353594b0f8e8587a19c621d7f8f497599554 --- /dev/null +++ b/src/f5_tts/src/f5_tts/train/README.md @@ -0,0 +1,92 @@ +# Training + +Check your FFmpeg installation: +```bash +ffmpeg -version +``` +If not found, install it first (or skip assuming you know of other backends available). + +## Prepare Dataset + +Example data processing scripts, and you may tailor your own one along with a Dataset class in `src/f5_tts/model/dataset.py`. + +### 1. Some specific Datasets preparing scripts +Download corresponding dataset first, and fill in the path in scripts. + +```bash +# Prepare the Emilia dataset +python src/f5_tts/train/datasets/prepare_emilia.py + +# Prepare the Wenetspeech4TTS dataset +python src/f5_tts/train/datasets/prepare_wenetspeech4tts.py + +# Prepare the LibriTTS dataset +python src/f5_tts/train/datasets/prepare_libritts.py + +# Prepare the LJSpeech dataset +python src/f5_tts/train/datasets/prepare_ljspeech.py +``` + +### 2. Create custom dataset with metadata.csv +Use guidance see [#57 here](https://github.com/SWivid/F5-TTS/discussions/57#discussioncomment-10959029). + +```bash +python src/f5_tts/train/datasets/prepare_csv_wavs.py +``` + +## Training & Finetuning + +Once your datasets are prepared, you can start the training process. + +### 1. Training script used for pretrained model + +```bash +# setup accelerate config, e.g. use multi-gpu ddp, fp16 +# will be to: ~/.cache/huggingface/accelerate/default_config.yaml +accelerate config + +# .yaml files are under src/f5_tts/configs directory +accelerate launch src/f5_tts/train/train.py --config-name F5TTS_v1_Base.yaml + +# possible to overwrite accelerate and hydra config +accelerate launch --mixed_precision=fp16 src/f5_tts/train/train.py --config-name F5TTS_v1_Base.yaml ++datasets.batch_size_per_gpu=19200 +``` + +### 2. Finetuning practice +Discussion board for Finetuning [#57](https://github.com/SWivid/F5-TTS/discussions/57). + +Gradio UI training/finetuning with `src/f5_tts/train/finetune_gradio.py` see [#143](https://github.com/SWivid/F5-TTS/discussions/143). + +If want to finetune with a variant version e.g. *F5TTS_v1_Base_no_zero_init*, manually download pretrained checkpoint from model weight repository and fill in the path correspondingly on web interface. + +If use tensorboard as logger, install it first with `pip install tensorboard`. + +The `use_ema = True` might be harmful for early-stage finetuned checkpoints (which goes just few updates, thus ema weights still dominated by pretrained ones), try turn it off with finetune gradio option or `load_model(..., use_ema=False)`, see if offer better results. + +### 3. W&B Logging + +The `wandb/` dir will be created under path you run training/finetuning scripts. + +By default, the training script does NOT use logging (assuming you didn't manually log in using `wandb login`). + +To turn on wandb logging, you can either: + +1. Manually login with `wandb login`: Learn more [here](https://docs.wandb.ai/ref/cli/wandb-login) +2. Automatically login programmatically by setting an environment variable: Get an API KEY at https://wandb.ai/authorize and set the environment variable as follows: + +On Mac & Linux: + +``` +export WANDB_API_KEY= +``` + +On Windows: + +``` +set WANDB_API_KEY= +``` +Moreover, if you couldn't access W&B and want to log metrics offline, you can set the environment variable as follows: + +``` +export WANDB_MODE=offline +``` diff --git a/src/f5_tts/src/f5_tts/train/datasets/prepare_csv_wavs.py b/src/f5_tts/src/f5_tts/train/datasets/prepare_csv_wavs.py new file mode 100644 index 0000000000000000000000000000000000000000..dc953935d7816114746ff69499f18332bfe578ba --- /dev/null +++ b/src/f5_tts/src/f5_tts/train/datasets/prepare_csv_wavs.py @@ -0,0 +1,283 @@ +import concurrent.futures +import multiprocessing +import os +import shutil +import signal +import subprocess # For invoking ffprobe +import sys +from contextlib import contextmanager + + +sys.path.append(os.getcwd()) + +import argparse +import csv +import json +from importlib.resources import files +from pathlib import Path + +import torchaudio +from datasets.arrow_writer import ArrowWriter +from tqdm import tqdm + +from f5_tts.model.utils import convert_char_to_pinyin + + +PRETRAINED_VOCAB_PATH = files("f5_tts").joinpath("../../data/Emilia_ZH_EN_pinyin/vocab.txt") + + +def is_csv_wavs_format(input_dataset_dir): + fpath = Path(input_dataset_dir) + metadata = fpath / "metadata.csv" + wavs = fpath / "wavs" + return metadata.exists() and metadata.is_file() and wavs.exists() and wavs.is_dir() + + +# Configuration constants +BATCH_SIZE = 100 # Batch size for text conversion +MAX_WORKERS = max(1, multiprocessing.cpu_count() - 1) # Leave one CPU free +THREAD_NAME_PREFIX = "AudioProcessor" +CHUNK_SIZE = 100 # Number of files to process per worker batch + +executor = None # Global executor for cleanup + + +@contextmanager +def graceful_exit(): + """Context manager for graceful shutdown on signals""" + + def signal_handler(signum, frame): + print("\nReceived signal to terminate. Cleaning up...") + if executor is not None: + print("Shutting down executor...") + executor.shutdown(wait=False, cancel_futures=True) + sys.exit(1) + + # Set up signal handlers + signal.signal(signal.SIGINT, signal_handler) + signal.signal(signal.SIGTERM, signal_handler) + + try: + yield + finally: + if executor is not None: + executor.shutdown(wait=False) + + +def process_audio_file(audio_path, text, polyphone): + """Process a single audio file by checking its existence and extracting duration.""" + if not Path(audio_path).exists(): + print(f"audio {audio_path} not found, skipping") + return None + try: + audio_duration = get_audio_duration(audio_path) + if audio_duration <= 0: + raise ValueError(f"Duration {audio_duration} is non-positive.") + return (audio_path, text, audio_duration) + except Exception as e: + print(f"Warning: Failed to process {audio_path} due to error: {e}. Skipping corrupt file.") + return None + + +def batch_convert_texts(texts, polyphone, batch_size=BATCH_SIZE): + """Convert a list of texts to pinyin in batches.""" + converted_texts = [] + for i in range(0, len(texts), batch_size): + batch = texts[i : i + batch_size] + converted_batch = convert_char_to_pinyin(batch, polyphone=polyphone) + converted_texts.extend(converted_batch) + return converted_texts + + +def prepare_csv_wavs_dir(input_dir, num_workers=None): + global executor + assert is_csv_wavs_format(input_dir), f"not csv_wavs format: {input_dir}" + input_dir = Path(input_dir) + metadata_path = input_dir / "metadata.csv" + audio_path_text_pairs = read_audio_text_pairs(metadata_path.as_posix()) + + polyphone = True + total_files = len(audio_path_text_pairs) + + # Use provided worker count or calculate optimal number + worker_count = num_workers if num_workers is not None else min(MAX_WORKERS, total_files) + print(f"\nProcessing {total_files} audio files using {worker_count} workers...") + + with graceful_exit(): + # Initialize thread pool with optimized settings + with concurrent.futures.ThreadPoolExecutor( + max_workers=worker_count, thread_name_prefix=THREAD_NAME_PREFIX + ) as exec: + executor = exec + results = [] + + # Process files in chunks for better efficiency + for i in range(0, len(audio_path_text_pairs), CHUNK_SIZE): + chunk = audio_path_text_pairs[i : i + CHUNK_SIZE] + # Submit futures in order + chunk_futures = [executor.submit(process_audio_file, pair[0], pair[1], polyphone) for pair in chunk] + + # Iterate over futures in the original submission order to preserve ordering + for future in tqdm( + chunk_futures, + total=len(chunk), + desc=f"Processing chunk {i // CHUNK_SIZE + 1}/{(total_files + CHUNK_SIZE - 1) // CHUNK_SIZE}", + ): + try: + result = future.result() + if result is not None: + results.append(result) + except Exception as e: + print(f"Error processing file: {e}") + + executor = None + + # Filter out failed results + processed = [res for res in results if res is not None] + if not processed: + raise RuntimeError("No valid audio files were processed!") + + # Batch process text conversion + raw_texts = [item[1] for item in processed] + converted_texts = batch_convert_texts(raw_texts, polyphone, batch_size=BATCH_SIZE) + + # Prepare final results + sub_result = [] + durations = [] + vocab_set = set() + + for (audio_path, _, duration), conv_text in zip(processed, converted_texts): + sub_result.append({"audio_path": audio_path, "text": conv_text, "duration": duration}) + durations.append(duration) + vocab_set.update(list(conv_text)) + + return sub_result, durations, vocab_set + + +def get_audio_duration(audio_path, timeout=5): + """ + Get the duration of an audio file in seconds using ffmpeg's ffprobe. + Falls back to torchaudio.load() if ffprobe fails. + """ + try: + cmd = [ + "ffprobe", + "-v", + "error", + "-show_entries", + "format=duration", + "-of", + "default=noprint_wrappers=1:nokey=1", + audio_path, + ] + result = subprocess.run( + cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, check=True, timeout=timeout + ) + duration_str = result.stdout.strip() + if duration_str: + return float(duration_str) + raise ValueError("Empty duration string from ffprobe.") + except (subprocess.TimeoutExpired, subprocess.SubprocessError, ValueError) as e: + print(f"Warning: ffprobe failed for {audio_path} with error: {e}. Falling back to torchaudio.") + try: + audio, sample_rate = torchaudio.load(audio_path) + return audio.shape[1] / sample_rate + except Exception as e: + raise RuntimeError(f"Both ffprobe and torchaudio failed for {audio_path}: {e}") + + +def read_audio_text_pairs(csv_file_path): + audio_text_pairs = [] + + parent = Path(csv_file_path).parent + with open(csv_file_path, mode="r", newline="", encoding="utf-8-sig") as csvfile: + reader = csv.reader(csvfile, delimiter="|") + next(reader) # Skip the header row + for row in reader: + if len(row) >= 2: + audio_file = row[0].strip() # First column: audio file path + text = row[1].strip() # Second column: text + audio_file_path = parent / audio_file + audio_text_pairs.append((audio_file_path.as_posix(), text)) + + return audio_text_pairs + + +def save_prepped_dataset(out_dir, result, duration_list, text_vocab_set, is_finetune): + out_dir = Path(out_dir) + out_dir.mkdir(exist_ok=True, parents=True) + print(f"\nSaving to {out_dir} ...") + + raw_arrow_path = out_dir / "raw.arrow" + with ArrowWriter(path=raw_arrow_path.as_posix()) as writer: + for line in tqdm(result, desc="Writing to raw.arrow ..."): + writer.write(line) + writer.finalize() + + # Save durations to JSON + dur_json_path = out_dir / "duration.json" + with open(dur_json_path.as_posix(), "w", encoding="utf-8") as f: + json.dump({"duration": duration_list}, f, ensure_ascii=False) + + # Handle vocab file - write only once based on finetune flag + voca_out_path = out_dir / "vocab.txt" + if is_finetune: + file_vocab_finetune = PRETRAINED_VOCAB_PATH.as_posix() + shutil.copy2(file_vocab_finetune, voca_out_path) + else: + with open(voca_out_path.as_posix(), "w") as f: + for vocab in sorted(text_vocab_set): + f.write(vocab + "\n") + + dataset_name = out_dir.stem + print(f"\nFor {dataset_name}, sample count: {len(result)}") + print(f"For {dataset_name}, vocab size is: {len(text_vocab_set)}") + print(f"For {dataset_name}, total {sum(duration_list) / 3600:.2f} hours") + + +def prepare_and_save_set(inp_dir, out_dir, is_finetune: bool = True, num_workers: int = None): + if is_finetune: + assert PRETRAINED_VOCAB_PATH.exists(), f"pretrained vocab.txt not found: {PRETRAINED_VOCAB_PATH}" + sub_result, durations, vocab_set = prepare_csv_wavs_dir(inp_dir, num_workers=num_workers) + save_prepped_dataset(out_dir, sub_result, durations, vocab_set, is_finetune) + + +def cli(): + try: + # Before processing, check if ffprobe is available. + if shutil.which("ffprobe") is None: + print( + "Warning: ffprobe is not available. Duration extraction will rely on torchaudio (which may be slower)." + ) + + # Usage examples in help text + parser = argparse.ArgumentParser( + description="Prepare and save dataset.", + epilog=""" +Examples: + # For fine-tuning (default): + python prepare_csv_wavs.py /input/dataset/path /output/dataset/path + + # For pre-training: + python prepare_csv_wavs.py /input/dataset/path /output/dataset/path --pretrain + + # With custom worker count: + python prepare_csv_wavs.py /input/dataset/path /output/dataset/path --workers 4 + """, + ) + parser.add_argument("inp_dir", type=str, help="Input directory containing the data.") + parser.add_argument("out_dir", type=str, help="Output directory to save the prepared data.") + parser.add_argument("--pretrain", action="store_true", help="Enable for new pretrain, otherwise is a fine-tune") + parser.add_argument("--workers", type=int, help=f"Number of worker threads (default: {MAX_WORKERS})") + args = parser.parse_args() + + prepare_and_save_set(args.inp_dir, args.out_dir, is_finetune=not args.pretrain, num_workers=args.workers) + except KeyboardInterrupt: + print("\nOperation cancelled by user. Cleaning up...") + if executor is not None: + executor.shutdown(wait=False, cancel_futures=True) + sys.exit(1) + + +if __name__ == "__main__": + cli() diff --git a/src/f5_tts/src/f5_tts/train/datasets/prepare_emilia.py b/src/f5_tts/src/f5_tts/train/datasets/prepare_emilia.py new file mode 100644 index 0000000000000000000000000000000000000000..3cfe468f1a1d470bf4d422615457a11faa5b2456 --- /dev/null +++ b/src/f5_tts/src/f5_tts/train/datasets/prepare_emilia.py @@ -0,0 +1,229 @@ +# Emilia Dataset: https://huggingface.co/datasets/amphion/Emilia-Dataset/tree/fc71e07 +# if use updated new version, i.e. WebDataset, feel free to modify / draft your own script + +# generate audio text map for Emilia ZH & EN +# evaluate for vocab size + +import os +import sys + + +sys.path.append(os.getcwd()) + +import json +from concurrent.futures import ProcessPoolExecutor +from importlib.resources import files +from pathlib import Path + +from datasets.arrow_writer import ArrowWriter +from tqdm import tqdm + +from f5_tts.model.utils import convert_char_to_pinyin, repetition_found + + +out_zh = { + "ZH_B00041_S06226", + "ZH_B00042_S09204", + "ZH_B00065_S09430", + "ZH_B00065_S09431", + "ZH_B00066_S09327", + "ZH_B00066_S09328", +} +zh_filters = ["い", "て"] +# seems synthesized audios, or heavily code-switched +out_en = { + "EN_B00013_S00913", + "EN_B00042_S00120", + "EN_B00055_S04111", + "EN_B00061_S00693", + "EN_B00061_S01494", + "EN_B00061_S03375", + "EN_B00059_S00092", + "EN_B00111_S04300", + "EN_B00100_S03759", + "EN_B00087_S03811", + "EN_B00059_S00950", + "EN_B00089_S00946", + "EN_B00078_S05127", + "EN_B00070_S04089", + "EN_B00074_S09659", + "EN_B00061_S06983", + "EN_B00061_S07060", + "EN_B00059_S08397", + "EN_B00082_S06192", + "EN_B00091_S01238", + "EN_B00089_S07349", + "EN_B00070_S04343", + "EN_B00061_S02400", + "EN_B00076_S01262", + "EN_B00068_S06467", + "EN_B00076_S02943", + "EN_B00064_S05954", + "EN_B00061_S05386", + "EN_B00066_S06544", + "EN_B00076_S06944", + "EN_B00072_S08620", + "EN_B00076_S07135", + "EN_B00076_S09127", + "EN_B00065_S00497", + "EN_B00059_S06227", + "EN_B00063_S02859", + "EN_B00075_S01547", + "EN_B00061_S08286", + "EN_B00079_S02901", + "EN_B00092_S03643", + "EN_B00096_S08653", + "EN_B00063_S04297", + "EN_B00063_S04614", + "EN_B00079_S04698", + "EN_B00104_S01666", + "EN_B00061_S09504", + "EN_B00061_S09694", + "EN_B00065_S05444", + "EN_B00063_S06860", + "EN_B00065_S05725", + "EN_B00069_S07628", + "EN_B00083_S03875", + "EN_B00071_S07665", + "EN_B00071_S07665", + "EN_B00062_S04187", + "EN_B00065_S09873", + "EN_B00065_S09922", + "EN_B00084_S02463", + "EN_B00067_S05066", + "EN_B00106_S08060", + "EN_B00073_S06399", + "EN_B00073_S09236", + "EN_B00087_S00432", + "EN_B00085_S05618", + "EN_B00064_S01262", + "EN_B00072_S01739", + "EN_B00059_S03913", + "EN_B00069_S04036", + "EN_B00067_S05623", + "EN_B00060_S05389", + "EN_B00060_S07290", + "EN_B00062_S08995", +} +en_filters = ["ا", "い", "て"] + + +def deal_with_audio_dir(audio_dir): + audio_jsonl = audio_dir.with_suffix(".jsonl") + sub_result, durations = [], [] + vocab_set = set() + bad_case_zh = 0 + bad_case_en = 0 + with open(audio_jsonl, "r") as f: + lines = f.readlines() + for line in tqdm(lines, desc=f"{audio_jsonl.stem}"): + obj = json.loads(line) + text = obj["text"] + if obj["language"] == "zh": + if obj["wav"].split("/")[1] in out_zh or any(f in text for f in zh_filters) or repetition_found(text): + bad_case_zh += 1 + continue + else: + text = text.translate( + str.maketrans({",": ",", "!": "!", "?": "?"}) + ) # not "。" cuz much code-switched + if obj["language"] == "en": + if ( + obj["wav"].split("/")[1] in out_en + or any(f in text for f in en_filters) + or repetition_found(text, length=4) + ): + bad_case_en += 1 + continue + if tokenizer == "pinyin": + text = convert_char_to_pinyin([text], polyphone=polyphone)[0] + duration = obj["duration"] + sub_result.append({"audio_path": str(audio_dir.parent / obj["wav"]), "text": text, "duration": duration}) + durations.append(duration) + vocab_set.update(list(text)) + return sub_result, durations, vocab_set, bad_case_zh, bad_case_en + + +def main(): + assert tokenizer in ["pinyin", "char"] + result = [] + duration_list = [] + text_vocab_set = set() + total_bad_case_zh = 0 + total_bad_case_en = 0 + + # process raw data + executor = ProcessPoolExecutor(max_workers=max_workers) + futures = [] + for lang in langs: + dataset_path = Path(os.path.join(dataset_dir, lang)) + [ + futures.append(executor.submit(deal_with_audio_dir, audio_dir)) + for audio_dir in dataset_path.iterdir() + if audio_dir.is_dir() + ] + for futures in tqdm(futures, total=len(futures)): + sub_result, durations, vocab_set, bad_case_zh, bad_case_en = futures.result() + result.extend(sub_result) + duration_list.extend(durations) + text_vocab_set.update(vocab_set) + total_bad_case_zh += bad_case_zh + total_bad_case_en += bad_case_en + executor.shutdown() + + # save preprocessed dataset to disk + if not os.path.exists(f"{save_dir}"): + os.makedirs(f"{save_dir}") + print(f"\nSaving to {save_dir} ...") + + # dataset = Dataset.from_dict({"audio_path": audio_path_list, "text": text_list, "duration": duration_list}) # oom + # dataset.save_to_disk(f"{save_dir}/raw", max_shard_size="2GB") + with ArrowWriter(path=f"{save_dir}/raw.arrow") as writer: + for line in tqdm(result, desc="Writing to raw.arrow ..."): + writer.write(line) + writer.finalize() + + # dup a json separately saving duration in case for DynamicBatchSampler ease + with open(f"{save_dir}/duration.json", "w", encoding="utf-8") as f: + json.dump({"duration": duration_list}, f, ensure_ascii=False) + + # vocab map, i.e. tokenizer + # add alphabets and symbols (optional, if plan to ft on de/fr etc.) + # if tokenizer == "pinyin": + # text_vocab_set.update([chr(i) for i in range(32, 127)] + [chr(i) for i in range(192, 256)]) + with open(f"{save_dir}/vocab.txt", "w") as f: + for vocab in sorted(text_vocab_set): + f.write(vocab + "\n") + + print(f"\nFor {dataset_name}, sample count: {len(result)}") + print(f"For {dataset_name}, vocab size is: {len(text_vocab_set)}") + print(f"For {dataset_name}, total {sum(duration_list) / 3600:.2f} hours") + if "ZH" in langs: + print(f"Bad zh transcription case: {total_bad_case_zh}") + if "EN" in langs: + print(f"Bad en transcription case: {total_bad_case_en}\n") + + +if __name__ == "__main__": + max_workers = 32 + + tokenizer = "pinyin" # "pinyin" | "char" + polyphone = True + + langs = ["ZH", "EN"] + dataset_dir = "/Emilia_Dataset/raw" + dataset_name = f"Emilia_{'_'.join(langs)}_{tokenizer}" + save_dir = str(files("f5_tts").joinpath("../../")) + f"/data/{dataset_name}" + print(f"\nPrepare for {dataset_name}, will save to {save_dir}\n") + + main() + + # Emilia ZH & EN + # samples count 37837916 (after removal) + # pinyin vocab size 2543 (polyphone) + # total duration 95281.87 (hours) + # bad zh asr cnt 230435 (samples) + # bad eh asr cnt 37217 (samples) + + # vocab size may be slightly different due to jieba tokenizer and pypinyin (e.g. way of polyphoneme) + # please be careful if using pretrained model, make sure the vocab.txt is same diff --git a/src/f5_tts/src/f5_tts/train/datasets/prepare_emilia_v2.py b/src/f5_tts/src/f5_tts/train/datasets/prepare_emilia_v2.py new file mode 100644 index 0000000000000000000000000000000000000000..67c1fb5133c5ae752cd1b39dae3870f8f20ac47b --- /dev/null +++ b/src/f5_tts/src/f5_tts/train/datasets/prepare_emilia_v2.py @@ -0,0 +1,95 @@ +# put in src/f5_tts/train/datasets/prepare_emilia_v2.py +# prepares Emilia dataset with the new format w/ Emilia-YODAS + +import json +import os +from concurrent.futures import ProcessPoolExecutor +from importlib.resources import files +from pathlib import Path + +from datasets.arrow_writer import ArrowWriter +from tqdm import tqdm + +from f5_tts.model.utils import repetition_found + + +# Define filters for exclusion +out_en = set() +en_filters = ["ا", "い", "て"] + + +def process_audio_directory(audio_dir): + sub_result, durations, vocab_set = [], [], set() + bad_case_en = 0 + + for file in audio_dir.iterdir(): + if file.suffix == ".json": + with open(file, "r") as f: + obj = json.load(f) + text = obj["text"] + if any(f in text for f in en_filters) or repetition_found(text, length=4): + bad_case_en += 1 + continue + + duration = obj["duration"] + audio_file = file.with_suffix(".mp3") + if audio_file.exists(): + sub_result.append({"audio_path": str(audio_file), "text": text, "duration": duration}) + durations.append(duration) + vocab_set.update(list(text)) + + return sub_result, durations, vocab_set, bad_case_en + + +def main(): + assert tokenizer in ["pinyin", "char"] + result, duration_list, text_vocab_set = [], [], set() + total_bad_case_en = 0 + + executor = ProcessPoolExecutor(max_workers=max_workers) + futures = [] + dataset_path = Path(dataset_dir) + for sub_dir in dataset_path.iterdir(): + if sub_dir.is_dir(): + futures.append(executor.submit(process_audio_directory, sub_dir)) + + for future in tqdm(futures, total=len(futures)): + sub_result, durations, vocab_set, bad_case_en = future.result() + result.extend(sub_result) + duration_list.extend(durations) + text_vocab_set.update(vocab_set) + total_bad_case_en += bad_case_en + + executor.shutdown() + + if not os.path.exists(f"{save_dir}"): + os.makedirs(f"{save_dir}") + + with ArrowWriter(path=f"{save_dir}/raw.arrow") as writer: + for line in tqdm(result, desc="Writing to raw.arrow ..."): + writer.write(line) + writer.finalize() + + with open(f"{save_dir}/duration.json", "w", encoding="utf-8") as f: + json.dump({"duration": duration_list}, f, ensure_ascii=False) + + with open(f"{save_dir}/vocab.txt", "w") as f: + for vocab in sorted(text_vocab_set): + f.write(vocab + "\n") + + print(f"For {dataset_name}, sample count: {len(result)}") + print(f"For {dataset_name}, vocab size is: {len(text_vocab_set)}") + print(f"For {dataset_name}, total {sum(duration_list) / 3600:.2f} hours") + print(f"Bad en transcription case: {total_bad_case_en}\n") + + +if __name__ == "__main__": + max_workers = 32 + tokenizer = "char" + dataset_dir = "/home/ubuntu/emilia-dataset/Emilia-YODAS/EN" + dataset_name = f"Emilia_EN_{tokenizer}" + # save_dir = os.path.expanduser(f"~/F5-TTS/data/{dataset_name}") + save_dir = str(files("f5_tts").joinpath("../../")) + f"/data/{dataset_name}" + + print(f"Prepare for {dataset_name}, will save to {save_dir}\n") + main() diff --git a/src/f5_tts/src/f5_tts/train/datasets/prepare_libritts.py b/src/f5_tts/src/f5_tts/train/datasets/prepare_libritts.py new file mode 100644 index 0000000000000000000000000000000000000000..f335979df41d78e0793e97b9623cadb05086f2ed --- /dev/null +++ b/src/f5_tts/src/f5_tts/train/datasets/prepare_libritts.py @@ -0,0 +1,95 @@ +import os +import sys + + +sys.path.append(os.getcwd()) + +import json +from concurrent.futures import ProcessPoolExecutor +from importlib.resources import files +from pathlib import Path + +import soundfile as sf +from datasets.arrow_writer import ArrowWriter +from tqdm import tqdm + + +def deal_with_audio_dir(audio_dir): + sub_result, durations = [], [] + vocab_set = set() + audio_lists = list(audio_dir.rglob("*.wav")) + + for line in audio_lists: + text_path = line.with_suffix(".normalized.txt") + text = open(text_path, "r").read().strip() + duration = sf.info(line).duration + if duration < 0.4 or duration > 30: + continue + sub_result.append({"audio_path": str(line), "text": text, "duration": duration}) + durations.append(duration) + vocab_set.update(list(text)) + return sub_result, durations, vocab_set + + +def main(): + result = [] + duration_list = [] + text_vocab_set = set() + + # process raw data + executor = ProcessPoolExecutor(max_workers=max_workers) + futures = [] + + for subset in tqdm(SUB_SET): + dataset_path = Path(os.path.join(dataset_dir, subset)) + [ + futures.append(executor.submit(deal_with_audio_dir, audio_dir)) + for audio_dir in dataset_path.iterdir() + if audio_dir.is_dir() + ] + for future in tqdm(futures, total=len(futures)): + sub_result, durations, vocab_set = future.result() + result.extend(sub_result) + duration_list.extend(durations) + text_vocab_set.update(vocab_set) + executor.shutdown() + + # save preprocessed dataset to disk + if not os.path.exists(f"{save_dir}"): + os.makedirs(f"{save_dir}") + print(f"\nSaving to {save_dir} ...") + + with ArrowWriter(path=f"{save_dir}/raw.arrow") as writer: + for line in tqdm(result, desc="Writing to raw.arrow ..."): + writer.write(line) + writer.finalize() + + # dup a json separately saving duration in case for DynamicBatchSampler ease + with open(f"{save_dir}/duration.json", "w", encoding="utf-8") as f: + json.dump({"duration": duration_list}, f, ensure_ascii=False) + + # vocab map, i.e. tokenizer + with open(f"{save_dir}/vocab.txt", "w") as f: + for vocab in sorted(text_vocab_set): + f.write(vocab + "\n") + + print(f"\nFor {dataset_name}, sample count: {len(result)}") + print(f"For {dataset_name}, vocab size is: {len(text_vocab_set)}") + print(f"For {dataset_name}, total {sum(duration_list) / 3600:.2f} hours") + + +if __name__ == "__main__": + max_workers = 36 + + tokenizer = "char" # "pinyin" | "char" + + SUB_SET = ["train-clean-100", "train-clean-360", "train-other-500"] + dataset_dir = "/LibriTTS" + dataset_name = f"LibriTTS_{'_'.join(SUB_SET)}_{tokenizer}".replace("train-clean-", "").replace("train-other-", "") + save_dir = str(files("f5_tts").joinpath("../../")) + f"/data/{dataset_name}" + print(f"\nPrepare for {dataset_name}, will save to {save_dir}\n") + main() + + # For LibriTTS_100_360_500_char, sample count: 354218 + # For LibriTTS_100_360_500_char, vocab size is: 78 + # For LibriTTS_100_360_500_char, total 554.09 hours diff --git a/src/f5_tts/src/f5_tts/train/datasets/prepare_ljspeech.py b/src/f5_tts/src/f5_tts/train/datasets/prepare_ljspeech.py new file mode 100644 index 0000000000000000000000000000000000000000..3330d5c012315042e2d2b022f3514157dae6ff68 --- /dev/null +++ b/src/f5_tts/src/f5_tts/train/datasets/prepare_ljspeech.py @@ -0,0 +1,68 @@ +import os +import sys + + +sys.path.append(os.getcwd()) + +import json +from importlib.resources import files +from pathlib import Path + +import soundfile as sf +from datasets.arrow_writer import ArrowWriter +from tqdm import tqdm + + +def main(): + result = [] + duration_list = [] + text_vocab_set = set() + + with open(meta_info, "r") as f: + lines = f.readlines() + for line in tqdm(lines): + uttr, text, norm_text = line.split("|") + norm_text = norm_text.strip() + wav_path = Path(dataset_dir) / "wavs" / f"{uttr}.wav" + duration = sf.info(wav_path).duration + if duration < 0.4 or duration > 30: + continue + result.append({"audio_path": str(wav_path), "text": norm_text, "duration": duration}) + duration_list.append(duration) + text_vocab_set.update(list(norm_text)) + + # save preprocessed dataset to disk + if not os.path.exists(f"{save_dir}"): + os.makedirs(f"{save_dir}") + print(f"\nSaving to {save_dir} ...") + + with ArrowWriter(path=f"{save_dir}/raw.arrow") as writer: + for line in tqdm(result, desc="Writing to raw.arrow ..."): + writer.write(line) + writer.finalize() + + # dup a json separately saving duration in case for DynamicBatchSampler ease + with open(f"{save_dir}/duration.json", "w", encoding="utf-8") as f: + json.dump({"duration": duration_list}, f, ensure_ascii=False) + + # vocab map, i.e. tokenizer + # add alphabets and symbols (optional, if plan to ft on de/fr etc.) + with open(f"{save_dir}/vocab.txt", "w") as f: + for vocab in sorted(text_vocab_set): + f.write(vocab + "\n") + + print(f"\nFor {dataset_name}, sample count: {len(result)}") + print(f"For {dataset_name}, vocab size is: {len(text_vocab_set)}") + print(f"For {dataset_name}, total {sum(duration_list) / 3600:.2f} hours") + + +if __name__ == "__main__": + tokenizer = "char" # "pinyin" | "char" + + dataset_dir = "/LJSpeech-1.1" + dataset_name = f"LJSpeech_{tokenizer}" + meta_info = os.path.join(dataset_dir, "metadata.csv") + save_dir = str(files("f5_tts").joinpath("../../")) + f"/data/{dataset_name}" + print(f"\nPrepare for {dataset_name}, will save to {save_dir}\n") + + main() diff --git a/src/f5_tts/src/f5_tts/train/datasets/prepare_wenetspeech4tts.py b/src/f5_tts/src/f5_tts/train/datasets/prepare_wenetspeech4tts.py new file mode 100644 index 0000000000000000000000000000000000000000..24e8f7d72b49def5fadb2624f66dafddd05dfcd2 --- /dev/null +++ b/src/f5_tts/src/f5_tts/train/datasets/prepare_wenetspeech4tts.py @@ -0,0 +1,126 @@ +# generate audio text map for WenetSpeech4TTS +# evaluate for vocab size + +import os +import sys + + +sys.path.append(os.getcwd()) + +import json +from concurrent.futures import ProcessPoolExecutor +from importlib.resources import files + +import torchaudio +from datasets import Dataset +from tqdm import tqdm + +from f5_tts.model.utils import convert_char_to_pinyin + + +def deal_with_sub_path_files(dataset_path, sub_path): + print(f"Dealing with: {sub_path}") + + text_dir = os.path.join(dataset_path, sub_path, "txts") + audio_dir = os.path.join(dataset_path, sub_path, "wavs") + text_files = os.listdir(text_dir) + + audio_paths, texts, durations = [], [], [] + for text_file in tqdm(text_files): + with open(os.path.join(text_dir, text_file), "r", encoding="utf-8") as file: + first_line = file.readline().split("\t") + audio_nm = first_line[0] + audio_path = os.path.join(audio_dir, audio_nm + ".wav") + text = first_line[1].strip() + + audio_paths.append(audio_path) + + if tokenizer == "pinyin": + texts.extend(convert_char_to_pinyin([text], polyphone=polyphone)) + elif tokenizer == "char": + texts.append(text) + + audio, sample_rate = torchaudio.load(audio_path) + durations.append(audio.shape[-1] / sample_rate) + + return audio_paths, texts, durations + + +def main(): + assert tokenizer in ["pinyin", "char"] + + audio_path_list, text_list, duration_list = [], [], [] + + executor = ProcessPoolExecutor(max_workers=max_workers) + futures = [] + for dataset_path in dataset_paths: + sub_items = os.listdir(dataset_path) + sub_paths = [item for item in sub_items if os.path.isdir(os.path.join(dataset_path, item))] + for sub_path in sub_paths: + futures.append(executor.submit(deal_with_sub_path_files, dataset_path, sub_path)) + for future in tqdm(futures, total=len(futures)): + audio_paths, texts, durations = future.result() + audio_path_list.extend(audio_paths) + text_list.extend(texts) + duration_list.extend(durations) + executor.shutdown() + + if not os.path.exists("data"): + os.makedirs("data") + + print(f"\nSaving to {save_dir} ...") + dataset = Dataset.from_dict({"audio_path": audio_path_list, "text": text_list, "duration": duration_list}) + dataset.save_to_disk(f"{save_dir}/raw", max_shard_size="2GB") # arrow format + + with open(f"{save_dir}/duration.json", "w", encoding="utf-8") as f: + json.dump( + {"duration": duration_list}, f, ensure_ascii=False + ) # dup a json separately saving duration in case for DynamicBatchSampler ease + + print("\nEvaluating vocab size (all characters and symbols / all phonemes) ...") + text_vocab_set = set() + for text in tqdm(text_list): + text_vocab_set.update(list(text)) + + # add alphabets and symbols (optional, if plan to ft on de/fr etc.) + if tokenizer == "pinyin": + text_vocab_set.update([chr(i) for i in range(32, 127)] + [chr(i) for i in range(192, 256)]) + + with open(f"{save_dir}/vocab.txt", "w") as f: + for vocab in sorted(text_vocab_set): + f.write(vocab + "\n") + print(f"\nFor {dataset_name}, sample count: {len(text_list)}") + print(f"For {dataset_name}, vocab size is: {len(text_vocab_set)}\n") + + +if __name__ == "__main__": + max_workers = 32 + + tokenizer = "pinyin" # "pinyin" | "char" + polyphone = True + dataset_choice = 1 # 1: Premium, 2: Standard, 3: Basic + + dataset_name = ( + ["WenetSpeech4TTS_Premium", "WenetSpeech4TTS_Standard", "WenetSpeech4TTS_Basic"][dataset_choice - 1] + + "_" + + tokenizer + ) + dataset_paths = [ + "/WenetSpeech4TTS/Basic", + "/WenetSpeech4TTS/Standard", + "/WenetSpeech4TTS/Premium", + ][-dataset_choice:] + save_dir = str(files("f5_tts").joinpath("../../")) + f"/data/{dataset_name}" + print(f"\nChoose Dataset: {dataset_name}, will save to {save_dir}\n") + + main() + + # Results (if adding alphabets with accents and symbols): + # WenetSpeech4TTS Basic Standard Premium + # samples count 3932473 1941220 407494 + # pinyin vocab size 1349 1348 1344 (no polyphone) + # - - 1459 (polyphone) + # char vocab size 5264 5219 5042 + + # vocab size may be slightly different due to jieba tokenizer and pypinyin (e.g. way of polyphoneme) + # please be careful if using pretrained model, make sure the vocab.txt is same diff --git a/src/f5_tts/src/f5_tts/train/finetune_cli.py b/src/f5_tts/src/f5_tts/train/finetune_cli.py new file mode 100644 index 0000000000000000000000000000000000000000..d36f9f674fc5bb0e162aa43a59186cef7e756927 --- /dev/null +++ b/src/f5_tts/src/f5_tts/train/finetune_cli.py @@ -0,0 +1,214 @@ +import argparse +import os +import shutil +from importlib.resources import files + +from cached_path import cached_path + +from f5_tts.model import CFM, DiT, Trainer, UNetT +from f5_tts.model.dataset import load_dataset +from f5_tts.model.utils import get_tokenizer + + +# -------------------------- Dataset Settings --------------------------- # +target_sample_rate = 24000 +n_mel_channels = 100 +hop_length = 256 +win_length = 1024 +n_fft = 1024 +mel_spec_type = "vocos" # 'vocos' or 'bigvgan' + + +# -------------------------- Argument Parsing --------------------------- # +def parse_args(): + parser = argparse.ArgumentParser(description="Train CFM Model") + + parser.add_argument( + "--exp_name", + type=str, + default="F5TTS_v1_Base", + choices=["F5TTS_v1_Base", "F5TTS_Base", "E2TTS_Base"], + help="Experiment name", + ) + parser.add_argument("--dataset_name", type=str, default="Emilia_ZH_EN", help="Name of the dataset to use") + parser.add_argument("--learning_rate", type=float, default=1e-5, help="Learning rate for training") + parser.add_argument("--batch_size_per_gpu", type=int, default=3200, help="Batch size per GPU") + parser.add_argument( + "--batch_size_type", type=str, default="frame", choices=["frame", "sample"], help="Batch size type" + ) + parser.add_argument("--max_samples", type=int, default=64, help="Max sequences per batch") + parser.add_argument("--grad_accumulation_steps", type=int, default=1, help="Gradient accumulation steps") + parser.add_argument("--max_grad_norm", type=float, default=1.0, help="Max gradient norm for clipping") + parser.add_argument("--epochs", type=int, default=100, help="Number of training epochs") + parser.add_argument("--num_warmup_updates", type=int, default=20000, help="Warmup updates") + parser.add_argument("--save_per_updates", type=int, default=50000, help="Save checkpoint every N updates") + parser.add_argument( + "--keep_last_n_checkpoints", + type=int, + default=-1, + help="-1 to keep all, 0 to not save intermediate, > 0 to keep last N checkpoints", + ) + parser.add_argument("--last_per_updates", type=int, default=5000, help="Save last checkpoint every N updates") + parser.add_argument("--finetune", action="store_true", help="Use Finetune") + parser.add_argument("--pretrain", type=str, default=None, help="the path to the checkpoint") + parser.add_argument( + "--tokenizer", type=str, default="pinyin", choices=["pinyin", "char", "custom"], help="Tokenizer type" + ) + parser.add_argument( + "--tokenizer_path", + type=str, + default=None, + help="Path to custom tokenizer vocab file (only used if tokenizer = 'custom')", + ) + parser.add_argument( + "--log_samples", + action="store_true", + help="Log inferenced samples per ckpt save updates", + ) + parser.add_argument("--logger", type=str, default=None, choices=[None, "wandb", "tensorboard"], help="logger") + parser.add_argument( + "--bnb_optimizer", + action="store_true", + help="Use 8-bit Adam optimizer from bitsandbytes", + ) + + return parser.parse_args() + + +# -------------------------- Training Settings -------------------------- # + + +def main(): + args = parse_args() + + checkpoint_path = str(files("f5_tts").joinpath(f"../../ckpts/{args.dataset_name}")) + + # Model parameters based on experiment name + + if args.exp_name == "F5TTS_v1_Base": + wandb_resume_id = None + model_cls = DiT + model_cfg = dict( + dim=1024, + depth=22, + heads=16, + ff_mult=2, + text_dim=512, + conv_layers=4, + ) + if args.finetune: + if args.pretrain is None: + ckpt_path = str(cached_path("hf://SWivid/F5-TTS/F5TTS_v1_Base/model_1250000.safetensors")) + else: + ckpt_path = args.pretrain + + elif args.exp_name == "F5TTS_Base": + wandb_resume_id = None + model_cls = DiT + model_cfg = dict( + dim=1024, + depth=22, + heads=16, + ff_mult=2, + text_dim=512, + text_mask_padding=False, + conv_layers=4, + pe_attn_head=1, + ) + if args.finetune: + if args.pretrain is None: + ckpt_path = str(cached_path("hf://SWivid/F5-TTS/F5TTS_Base/model_1200000.pt")) + else: + ckpt_path = args.pretrain + + elif args.exp_name == "E2TTS_Base": + wandb_resume_id = None + model_cls = UNetT + model_cfg = dict( + dim=1024, + depth=24, + heads=16, + ff_mult=4, + text_mask_padding=False, + pe_attn_head=1, + ) + if args.finetune: + if args.pretrain is None: + ckpt_path = str(cached_path("hf://SWivid/E2-TTS/E2TTS_Base/model_1200000.pt")) + else: + ckpt_path = args.pretrain + + if args.finetune: + if not os.path.isdir(checkpoint_path): + os.makedirs(checkpoint_path, exist_ok=True) + + file_checkpoint = os.path.basename(ckpt_path) + if not file_checkpoint.startswith("pretrained_"): # Change: Add 'pretrained_' prefix to copied model + file_checkpoint = "pretrained_" + file_checkpoint + file_checkpoint = os.path.join(checkpoint_path, file_checkpoint) + if not os.path.isfile(file_checkpoint): + shutil.copy2(ckpt_path, file_checkpoint) + print("copy checkpoint for finetune") + + # Use the tokenizer and tokenizer_path provided in the command line arguments + + tokenizer = args.tokenizer + if tokenizer == "custom": + if not args.tokenizer_path: + raise ValueError("Custom tokenizer selected, but no tokenizer_path provided.") + tokenizer_path = args.tokenizer_path + else: + tokenizer_path = args.dataset_name + + vocab_char_map, vocab_size = get_tokenizer(tokenizer_path, tokenizer) + + print("\nvocab : ", vocab_size) + print("\nvocoder : ", mel_spec_type) + + mel_spec_kwargs = dict( + n_fft=n_fft, + hop_length=hop_length, + win_length=win_length, + n_mel_channels=n_mel_channels, + target_sample_rate=target_sample_rate, + mel_spec_type=mel_spec_type, + ) + + model = CFM( + transformer=model_cls(**model_cfg, text_num_embeds=vocab_size, mel_dim=n_mel_channels), + mel_spec_kwargs=mel_spec_kwargs, + vocab_char_map=vocab_char_map, + ) + + trainer = Trainer( + model, + args.epochs, + args.learning_rate, + num_warmup_updates=args.num_warmup_updates, + save_per_updates=args.save_per_updates, + keep_last_n_checkpoints=args.keep_last_n_checkpoints, + checkpoint_path=checkpoint_path, + batch_size_per_gpu=args.batch_size_per_gpu, + batch_size_type=args.batch_size_type, + max_samples=args.max_samples, + grad_accumulation_steps=args.grad_accumulation_steps, + max_grad_norm=args.max_grad_norm, + logger=args.logger, + wandb_project=args.dataset_name, + wandb_run_name=args.exp_name, + wandb_resume_id=wandb_resume_id, + log_samples=args.log_samples, + last_per_updates=args.last_per_updates, + bnb_optimizer=args.bnb_optimizer, + ) + + train_dataset = load_dataset(args.dataset_name, tokenizer, mel_spec_kwargs=mel_spec_kwargs) + + trainer.train( + train_dataset, + resumable_with_seed=666, # seed for shuffling dataset + ) + + +if __name__ == "__main__": + main() diff --git a/src/f5_tts/src/f5_tts/train/finetune_gradio.py b/src/f5_tts/src/f5_tts/train/finetune_gradio.py new file mode 100644 index 0000000000000000000000000000000000000000..fc3494dccedc86eaf2abeba31e673ccef6a4aebb --- /dev/null +++ b/src/f5_tts/src/f5_tts/train/finetune_gradio.py @@ -0,0 +1,1866 @@ +import gc +import json +import os +import platform +import queue +import random +import re +import shutil +import signal +import subprocess +import sys +import tempfile +import threading +import time +from glob import glob +from importlib.resources import files + +import click +import gradio as gr +import librosa +import numpy as np +import psutil +import torch +import torchaudio +from cached_path import cached_path +from datasets import Dataset as Dataset_ +from datasets.arrow_writer import ArrowWriter +from safetensors.torch import load_file, save_file +from scipy.io import wavfile + +from f5_tts.api import F5TTS +from f5_tts.infer.utils_infer import transcribe +from f5_tts.model.utils import convert_char_to_pinyin + + +training_process = None +system = platform.system() +python_executable = sys.executable or "python" +tts_api = None +last_checkpoint = "" +last_device = "" +last_ema = None + + +path_data = str(files("f5_tts").joinpath("../../data")) +path_project_ckpts = str(files("f5_tts").joinpath("../../ckpts")) +file_train = str(files("f5_tts").joinpath("train/finetune_cli.py")) + +device = ( + "cuda" + if torch.cuda.is_available() + else "xpu" + if torch.xpu.is_available() + else "mps" + if torch.backends.mps.is_available() + else "cpu" +) + + +# Save settings from a JSON file +def save_settings( + project_name, + exp_name, + learning_rate, + batch_size_per_gpu, + batch_size_type, + max_samples, + grad_accumulation_steps, + max_grad_norm, + epochs, + num_warmup_updates, + save_per_updates, + keep_last_n_checkpoints, + last_per_updates, + finetune, + file_checkpoint_train, + tokenizer_type, + tokenizer_file, + mixed_precision, + logger, + ch_8bit_adam, +): + path_project = os.path.join(path_project_ckpts, project_name) + os.makedirs(path_project, exist_ok=True) + file_setting = os.path.join(path_project, "setting.json") + + settings = { + "exp_name": exp_name, + "learning_rate": learning_rate, + "batch_size_per_gpu": batch_size_per_gpu, + "batch_size_type": batch_size_type, + "max_samples": max_samples, + "grad_accumulation_steps": grad_accumulation_steps, + "max_grad_norm": max_grad_norm, + "epochs": epochs, + "num_warmup_updates": num_warmup_updates, + "save_per_updates": save_per_updates, + "keep_last_n_checkpoints": keep_last_n_checkpoints, + "last_per_updates": last_per_updates, + "finetune": finetune, + "file_checkpoint_train": file_checkpoint_train, + "tokenizer_type": tokenizer_type, + "tokenizer_file": tokenizer_file, + "mixed_precision": mixed_precision, + "logger": logger, + "bnb_optimizer": ch_8bit_adam, + } + with open(file_setting, "w") as f: + json.dump(settings, f, indent=4) + return "Settings saved!" + + +# Load settings from a JSON file +def load_settings(project_name): + project_name = project_name.replace("_pinyin", "").replace("_char", "") + path_project = os.path.join(path_project_ckpts, project_name) + file_setting = os.path.join(path_project, "setting.json") + + # Default settings + default_settings = { + "exp_name": "F5TTS_v1_Base", + "learning_rate": 1e-5, + "batch_size_per_gpu": 3200, + "batch_size_type": "frame", + "max_samples": 64, + "grad_accumulation_steps": 1, + "max_grad_norm": 1.0, + "epochs": 100, + "num_warmup_updates": 100, + "save_per_updates": 500, + "keep_last_n_checkpoints": -1, + "last_per_updates": 100, + "finetune": True, + "file_checkpoint_train": "", + "tokenizer_type": "pinyin", + "tokenizer_file": "", + "mixed_precision": "fp16", + "logger": "none", + "bnb_optimizer": False, + } + if device == "mps": + default_settings["mixed_precision"] = "none" + + # Load settings from file if it exists + if os.path.isfile(file_setting): + with open(file_setting, "r") as f: + file_settings = json.load(f) + default_settings.update(file_settings) + + # Return as a tuple in the correct order + return ( + default_settings["exp_name"], + default_settings["learning_rate"], + default_settings["batch_size_per_gpu"], + default_settings["batch_size_type"], + default_settings["max_samples"], + default_settings["grad_accumulation_steps"], + default_settings["max_grad_norm"], + default_settings["epochs"], + default_settings["num_warmup_updates"], + default_settings["save_per_updates"], + default_settings["keep_last_n_checkpoints"], + default_settings["last_per_updates"], + default_settings["finetune"], + default_settings["file_checkpoint_train"], + default_settings["tokenizer_type"], + default_settings["tokenizer_file"], + default_settings["mixed_precision"], + default_settings["logger"], + default_settings["bnb_optimizer"], + ) + + +# Load metadata +def get_audio_duration(audio_path): + """Calculate the duration mono of an audio file.""" + audio, sample_rate = torchaudio.load(audio_path) + return audio.shape[1] / sample_rate + + +class Slicer: # https://github.com/RVC-Boss/GPT-SoVITS/blob/main/tools/slicer2.py + def __init__( + self, + sr: int, + threshold: float = -40.0, + min_length: int = 20000, # 20 seconds + min_interval: int = 300, + hop_size: int = 20, + max_sil_kept: int = 2000, + ): + if not min_length >= min_interval >= hop_size: + raise ValueError("The following condition must be satisfied: min_length >= min_interval >= hop_size") + if not max_sil_kept >= hop_size: + raise ValueError("The following condition must be satisfied: max_sil_kept >= hop_size") + min_interval = sr * min_interval / 1000 + self.threshold = 10 ** (threshold / 20.0) + self.hop_size = round(sr * hop_size / 1000) + self.win_size = min(round(min_interval), 4 * self.hop_size) + self.min_length = round(sr * min_length / 1000 / self.hop_size) + self.min_interval = round(min_interval / self.hop_size) + self.max_sil_kept = round(sr * max_sil_kept / 1000 / self.hop_size) + + def _apply_slice(self, waveform, begin, end): + if len(waveform.shape) > 1: + return waveform[:, begin * self.hop_size : min(waveform.shape[1], end * self.hop_size)] + else: + return waveform[begin * self.hop_size : min(waveform.shape[0], end * self.hop_size)] + + # @timeit + def slice(self, waveform): + if len(waveform.shape) > 1: + samples = waveform.mean(axis=0) + else: + samples = waveform + if samples.shape[0] <= self.min_length: + return [waveform] + rms_list = librosa.feature.rms(y=samples, frame_length=self.win_size, hop_length=self.hop_size).squeeze(0) + sil_tags = [] + silence_start = None + clip_start = 0 + for i, rms in enumerate(rms_list): + # Keep looping while frame is silent. + if rms < self.threshold: + # Record start of silent frames. + if silence_start is None: + silence_start = i + continue + # Keep looping while frame is not silent and silence start has not been recorded. + if silence_start is None: + continue + # Clear recorded silence start if interval is not enough or clip is too short + is_leading_silence = silence_start == 0 and i > self.max_sil_kept + need_slice_middle = i - silence_start >= self.min_interval and i - clip_start >= self.min_length + if not is_leading_silence and not need_slice_middle: + silence_start = None + continue + # Need slicing. Record the range of silent frames to be removed. + if i - silence_start <= self.max_sil_kept: + pos = rms_list[silence_start : i + 1].argmin() + silence_start + if silence_start == 0: + sil_tags.append((0, pos)) + else: + sil_tags.append((pos, pos)) + clip_start = pos + elif i - silence_start <= self.max_sil_kept * 2: + pos = rms_list[i - self.max_sil_kept : silence_start + self.max_sil_kept + 1].argmin() + pos += i - self.max_sil_kept + pos_l = rms_list[silence_start : silence_start + self.max_sil_kept + 1].argmin() + silence_start + pos_r = rms_list[i - self.max_sil_kept : i + 1].argmin() + i - self.max_sil_kept + if silence_start == 0: + sil_tags.append((0, pos_r)) + clip_start = pos_r + else: + sil_tags.append((min(pos_l, pos), max(pos_r, pos))) + clip_start = max(pos_r, pos) + else: + pos_l = rms_list[silence_start : silence_start + self.max_sil_kept + 1].argmin() + silence_start + pos_r = rms_list[i - self.max_sil_kept : i + 1].argmin() + i - self.max_sil_kept + if silence_start == 0: + sil_tags.append((0, pos_r)) + else: + sil_tags.append((pos_l, pos_r)) + clip_start = pos_r + silence_start = None + # Deal with trailing silence. + total_frames = rms_list.shape[0] + if silence_start is not None and total_frames - silence_start >= self.min_interval: + silence_end = min(total_frames, silence_start + self.max_sil_kept) + pos = rms_list[silence_start : silence_end + 1].argmin() + silence_start + sil_tags.append((pos, total_frames + 1)) + # Apply and return slices: [chunk, start, end] + if len(sil_tags) == 0: + return [[waveform, 0, int(total_frames * self.hop_size)]] + else: + chunks = [] + if sil_tags[0][0] > 0: + chunks.append([self._apply_slice(waveform, 0, sil_tags[0][0]), 0, int(sil_tags[0][0] * self.hop_size)]) + for i in range(len(sil_tags) - 1): + chunks.append( + [ + self._apply_slice(waveform, sil_tags[i][1], sil_tags[i + 1][0]), + int(sil_tags[i][1] * self.hop_size), + int(sil_tags[i + 1][0] * self.hop_size), + ] + ) + if sil_tags[-1][1] < total_frames: + chunks.append( + [ + self._apply_slice(waveform, sil_tags[-1][1], total_frames), + int(sil_tags[-1][1] * self.hop_size), + int(total_frames * self.hop_size), + ] + ) + return chunks + + +# terminal +def terminate_process_tree(pid, including_parent=True): + try: + parent = psutil.Process(pid) + except psutil.NoSuchProcess: + # Process already terminated + return + + children = parent.children(recursive=True) + for child in children: + try: + os.kill(child.pid, signal.SIGTERM) # or signal.SIGKILL + except OSError: + pass + if including_parent: + try: + os.kill(parent.pid, signal.SIGTERM) # or signal.SIGKILL + except OSError: + pass + + +def terminate_process(pid): + if system == "Windows": + cmd = f"taskkill /t /f /pid {pid}" + os.system(cmd) + else: + terminate_process_tree(pid) + + +def start_training( + dataset_name, + exp_name, + learning_rate, + batch_size_per_gpu, + batch_size_type, + max_samples, + grad_accumulation_steps, + max_grad_norm, + epochs, + num_warmup_updates, + save_per_updates, + keep_last_n_checkpoints, + last_per_updates, + finetune, + file_checkpoint_train, + tokenizer_type, + tokenizer_file, + mixed_precision, + stream, + logger, + ch_8bit_adam, +): + global training_process, tts_api, stop_signal + + if tts_api is not None: + if tts_api is not None: + del tts_api + + gc.collect() + torch.cuda.empty_cache() + tts_api = None + + path_project = os.path.join(path_data, dataset_name) + + if not os.path.isdir(path_project): + yield ( + f"There is not project with name {dataset_name}", + gr.update(interactive=True), + gr.update(interactive=False), + ) + return + + file_raw = os.path.join(path_project, "raw.arrow") + if not os.path.isfile(file_raw): + yield f"There is no file {file_raw}", gr.update(interactive=True), gr.update(interactive=False) + return + + # Check if a training process is already running + if training_process is not None: + return "Train run already!", gr.update(interactive=False), gr.update(interactive=True) + + yield "start train", gr.update(interactive=False), gr.update(interactive=False) + + # Command to run the training script with the specified arguments + + if tokenizer_file == "": + if dataset_name.endswith("_pinyin"): + tokenizer_type = "pinyin" + elif dataset_name.endswith("_char"): + tokenizer_type = "char" + else: + tokenizer_type = "custom" + + dataset_name = dataset_name.replace("_pinyin", "").replace("_char", "") + + if mixed_precision != "none": + fp16 = f"--mixed_precision={mixed_precision}" + else: + fp16 = "" + + cmd = ( + f'accelerate launch {fp16} "{file_train}" --exp_name {exp_name}' + f" --learning_rate {learning_rate}" + f" --batch_size_per_gpu {batch_size_per_gpu}" + f" --batch_size_type {batch_size_type}" + f" --max_samples {max_samples}" + f" --grad_accumulation_steps {grad_accumulation_steps}" + f" --max_grad_norm {max_grad_norm}" + f" --epochs {epochs}" + f" --num_warmup_updates {num_warmup_updates}" + f" --save_per_updates {save_per_updates}" + f" --keep_last_n_checkpoints {keep_last_n_checkpoints}" + f" --last_per_updates {last_per_updates}" + f" --dataset_name {dataset_name}" + ) + + if finetune: + cmd += " --finetune" + + if file_checkpoint_train != "": + cmd += f' --pretrain "{file_checkpoint_train}"' + + if tokenizer_file != "": + cmd += f" --tokenizer_path {tokenizer_file}" + + cmd += f" --tokenizer {tokenizer_type}" + + if logger != "none": + cmd += f" --logger {logger}" + + cmd += " --log_samples" + + if ch_8bit_adam: + cmd += " --bnb_optimizer" + + print("run command : \n" + cmd + "\n") + + save_settings( + dataset_name, + exp_name, + learning_rate, + batch_size_per_gpu, + batch_size_type, + max_samples, + grad_accumulation_steps, + max_grad_norm, + epochs, + num_warmup_updates, + save_per_updates, + keep_last_n_checkpoints, + last_per_updates, + finetune, + file_checkpoint_train, + tokenizer_type, + tokenizer_file, + mixed_precision, + logger, + ch_8bit_adam, + ) + + try: + if not stream: + # Start the training process + training_process = subprocess.Popen(cmd, shell=True) + + time.sleep(5) + yield "train start", gr.update(interactive=False), gr.update(interactive=True) + + # Wait for the training process to finish + training_process.wait() + else: + + def stream_output(pipe, output_queue): + try: + for line in iter(pipe.readline, ""): + output_queue.put(line) + except Exception as e: + output_queue.put(f"Error reading pipe: {str(e)}") + finally: + pipe.close() + + env = os.environ.copy() + env["PYTHONUNBUFFERED"] = "1" + + training_process = subprocess.Popen( + cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, bufsize=1, env=env + ) + yield "Training started ...", gr.update(interactive=False), gr.update(interactive=True) + + stdout_queue = queue.Queue() + stderr_queue = queue.Queue() + + stdout_thread = threading.Thread(target=stream_output, args=(training_process.stdout, stdout_queue)) + stderr_thread = threading.Thread(target=stream_output, args=(training_process.stderr, stderr_queue)) + stdout_thread.daemon = True + stderr_thread.daemon = True + stdout_thread.start() + stderr_thread.start() + stop_signal = False + while True: + if stop_signal: + training_process.terminate() + time.sleep(0.5) + if training_process.poll() is None: + training_process.kill() + yield "Training stopped by user.", gr.update(interactive=True), gr.update(interactive=False) + break + + process_status = training_process.poll() + + # Handle stdout + try: + while True: + output = stdout_queue.get_nowait() + print(output, end="") + match = re.search( + r"Epoch (\d+)/(\d+):\s+(\d+)%\|.*\[(\d+:\d+)<.*?loss=(\d+\.\d+), update=(\d+)", output + ) + if match: + current_epoch = match.group(1) + total_epochs = match.group(2) + percent_complete = match.group(3) + elapsed_time = match.group(4) + loss = match.group(5) + current_update = match.group(6) + message = ( + f"Epoch: {current_epoch}/{total_epochs}, " + f"Progress: {percent_complete}%, " + f"Elapsed Time: {elapsed_time}, " + f"Loss: {loss}, " + f"Update: {current_update}" + ) + yield message, gr.update(interactive=False), gr.update(interactive=True) + elif output.strip(): + yield output, gr.update(interactive=False), gr.update(interactive=True) + except queue.Empty: + pass + + # Handle stderr + try: + while True: + error_output = stderr_queue.get_nowait() + print(error_output, end="") + if error_output.strip(): + yield f"{error_output.strip()}", gr.update(interactive=False), gr.update(interactive=True) + except queue.Empty: + pass + + if process_status is not None and stdout_queue.empty() and stderr_queue.empty(): + if process_status != 0: + yield ( + f"Process crashed with exit code {process_status}!", + gr.update(interactive=False), + gr.update(interactive=True), + ) + else: + yield ( + "Training complete or paused ...", + gr.update(interactive=False), + gr.update(interactive=True), + ) + break + + # Small sleep to prevent CPU thrashing + time.sleep(0.1) + + # Clean up + training_process.stdout.close() + training_process.stderr.close() + training_process.wait() + + time.sleep(1) + + if training_process is None: + text_info = "Train stopped !" + else: + text_info = "Train complete at end !" + + except Exception as e: # Catch all exceptions + # Ensure that we reset the training process variable in case of an error + text_info = f"An error occurred: {str(e)}" + + training_process = None + + yield text_info, gr.update(interactive=True), gr.update(interactive=False) + + +def stop_training(): + global training_process, stop_signal + + if training_process is None: + return "Train not running !", gr.update(interactive=True), gr.update(interactive=False) + terminate_process_tree(training_process.pid) + # training_process = None + stop_signal = True + return "Train stopped !", gr.update(interactive=True), gr.update(interactive=False) + + +def get_list_projects(): + project_list = [] + for folder in os.listdir(path_data): + path_folder = os.path.join(path_data, folder) + if not os.path.isdir(path_folder): + continue + folder = folder.lower() + if folder == "emilia_zh_en_pinyin": + continue + project_list.append(folder) + + projects_selelect = None if not project_list else project_list[-1] + + return project_list, projects_selelect + + +def create_data_project(name, tokenizer_type): + name += "_" + tokenizer_type + os.makedirs(os.path.join(path_data, name), exist_ok=True) + os.makedirs(os.path.join(path_data, name, "dataset"), exist_ok=True) + project_list, projects_selelect = get_list_projects() + return gr.update(choices=project_list, value=name) + + +def transcribe_all(name_project, audio_files, language, user=False, progress=gr.Progress()): + path_project = os.path.join(path_data, name_project) + path_dataset = os.path.join(path_project, "dataset") + path_project_wavs = os.path.join(path_project, "wavs") + file_metadata = os.path.join(path_project, "metadata.csv") + + if not user: + if audio_files is None: + return "You need to load an audio file." + + if os.path.isdir(path_project_wavs): + shutil.rmtree(path_project_wavs) + + if os.path.isfile(file_metadata): + os.remove(file_metadata) + + os.makedirs(path_project_wavs, exist_ok=True) + + if user: + file_audios = [ + file + for format in ("*.wav", "*.ogg", "*.opus", "*.mp3", "*.flac") + for file in glob(os.path.join(path_dataset, format)) + ] + if file_audios == []: + return "No audio file was found in the dataset." + else: + file_audios = audio_files + + alpha = 0.5 + _max = 1.0 + slicer = Slicer(24000) + + num = 0 + error_num = 0 + data = "" + for file_audio in progress.tqdm(file_audios, desc="transcribe files", total=len((file_audios))): + audio, _ = librosa.load(file_audio, sr=24000, mono=True) + + list_slicer = slicer.slice(audio) + for chunk, start, end in progress.tqdm(list_slicer, total=len(list_slicer), desc="slicer files"): + name_segment = os.path.join(f"segment_{num}") + file_segment = os.path.join(path_project_wavs, f"{name_segment}.wav") + + tmp_max = np.abs(chunk).max() + if tmp_max > 1: + chunk /= tmp_max + chunk = (chunk / tmp_max * (_max * alpha)) + (1 - alpha) * chunk + wavfile.write(file_segment, 24000, (chunk * 32767).astype(np.int16)) + + try: + text = transcribe(file_segment, language) + text = text.strip() + + data += f"{name_segment}|{text}\n" + + num += 1 + except: # noqa: E722 + error_num += 1 + + with open(file_metadata, "w", encoding="utf-8-sig") as f: + f.write(data) + + if error_num != []: + error_text = f"\nerror files : {error_num}" + else: + error_text = "" + + return f"transcribe complete samples : {num}\npath : {path_project_wavs}{error_text}" + + +def format_seconds_to_hms(seconds): + hours = int(seconds / 3600) + minutes = int((seconds % 3600) / 60) + seconds = seconds % 60 + return "{:02d}:{:02d}:{:02d}".format(hours, minutes, int(seconds)) + + +def get_correct_audio_path( + audio_input, + base_path="wavs", + supported_formats=("wav", "mp3", "aac", "flac", "m4a", "alac", "ogg", "aiff", "wma", "amr"), +): + file_audio = None + + # Helper function to check if file has a supported extension + def has_supported_extension(file_name): + return any(file_name.endswith(f".{ext}") for ext in supported_formats) + + # Case 1: If it's a full path with a valid extension, use it directly + if os.path.isabs(audio_input) and has_supported_extension(audio_input): + file_audio = audio_input + + # Case 2: If it has a supported extension but is not a full path + elif has_supported_extension(audio_input) and not os.path.isabs(audio_input): + file_audio = os.path.join(base_path, audio_input) + + # Case 3: If only the name is given (no extension and not a full path) + elif not has_supported_extension(audio_input) and not os.path.isabs(audio_input): + for ext in supported_formats: + potential_file = os.path.join(base_path, f"{audio_input}.{ext}") + if os.path.exists(potential_file): + file_audio = potential_file + break + else: + file_audio = os.path.join(base_path, f"{audio_input}.{supported_formats[0]}") + return file_audio + + +def create_metadata(name_project, ch_tokenizer, progress=gr.Progress()): + path_project = os.path.join(path_data, name_project) + path_project_wavs = os.path.join(path_project, "wavs") + file_metadata = os.path.join(path_project, "metadata.csv") + file_raw = os.path.join(path_project, "raw.arrow") + file_duration = os.path.join(path_project, "duration.json") + file_vocab = os.path.join(path_project, "vocab.txt") + + if not os.path.isfile(file_metadata): + return "The file was not found in " + file_metadata, "" + + with open(file_metadata, "r", encoding="utf-8-sig") as f: + data = f.read() + + audio_path_list = [] + text_list = [] + duration_list = [] + + count = data.split("\n") + lenght = 0 + result = [] + error_files = [] + text_vocab_set = set() + for line in progress.tqdm(data.split("\n"), total=count): + sp_line = line.split("|") + if len(sp_line) != 2: + continue + name_audio, text = sp_line[:2] + + file_audio = get_correct_audio_path(name_audio, path_project_wavs) + + if not os.path.isfile(file_audio): + error_files.append([file_audio, "error path"]) + continue + + try: + duration = get_audio_duration(file_audio) + except Exception as e: + error_files.append([file_audio, "duration"]) + print(f"Error processing {file_audio}: {e}") + continue + + if duration < 1 or duration > 30: + if duration > 30: + error_files.append([file_audio, "duration > 30 sec"]) + if duration < 1: + error_files.append([file_audio, "duration < 1 sec "]) + continue + if len(text) < 3: + error_files.append([file_audio, "very short text length 3"]) + continue + + text = text.strip() + text = convert_char_to_pinyin([text], polyphone=True)[0] + + audio_path_list.append(file_audio) + duration_list.append(duration) + text_list.append(text) + + result.append({"audio_path": file_audio, "text": text, "duration": duration}) + if ch_tokenizer: + text_vocab_set.update(list(text)) + + lenght += duration + + if duration_list == []: + return f"Error: No audio files found in the specified path : {path_project_wavs}", "" + + min_second = round(min(duration_list), 2) + max_second = round(max(duration_list), 2) + + with ArrowWriter(path=file_raw) as writer: + for line in progress.tqdm(result, total=len(result), desc="prepare data"): + writer.write(line) + writer.finalize() + + with open(file_duration, "w") as f: + json.dump({"duration": duration_list}, f, ensure_ascii=False) + + new_vocal = "" + if not ch_tokenizer: + if not os.path.isfile(file_vocab): + file_vocab_finetune = os.path.join(path_data, "Emilia_ZH_EN_pinyin/vocab.txt") + if not os.path.isfile(file_vocab_finetune): + return "Error: Vocabulary file 'Emilia_ZH_EN_pinyin' not found!", "" + shutil.copy2(file_vocab_finetune, file_vocab) + + with open(file_vocab, "r", encoding="utf-8-sig") as f: + vocab_char_map = {} + for i, char in enumerate(f): + vocab_char_map[char[:-1]] = i + vocab_size = len(vocab_char_map) + + else: + with open(file_vocab, "w", encoding="utf-8-sig") as f: + for vocab in sorted(text_vocab_set): + f.write(vocab + "\n") + new_vocal += vocab + "\n" + vocab_size = len(text_vocab_set) + + if error_files != []: + error_text = "\n".join([" = ".join(item) for item in error_files]) + else: + error_text = "" + + return ( + f"prepare complete \nsamples : {len(text_list)}\ntime data : {format_seconds_to_hms(lenght)}\nmin sec : {min_second}\nmax sec : {max_second}\nfile_arrow : {file_raw}\nvocab : {vocab_size}\n{error_text}", + new_vocal, + ) + + +def check_user(value): + return gr.update(visible=not value), gr.update(visible=value) + + +def calculate_train( + name_project, + epochs, + learning_rate, + batch_size_per_gpu, + batch_size_type, + max_samples, + num_warmup_updates, + finetune, +): + path_project = os.path.join(path_data, name_project) + file_duration = os.path.join(path_project, "duration.json") + + hop_length = 256 + sampling_rate = 24000 + + if not os.path.isfile(file_duration): + return ( + epochs, + learning_rate, + batch_size_per_gpu, + max_samples, + num_warmup_updates, + "project not found !", + ) + + with open(file_duration, "r") as file: + data = json.load(file) + + duration_list = data["duration"] + max_sample_length = max(duration_list) * sampling_rate / hop_length + total_samples = len(duration_list) + total_duration = sum(duration_list) + + if torch.cuda.is_available(): + gpu_count = torch.cuda.device_count() + total_memory = 0 + for i in range(gpu_count): + gpu_properties = torch.cuda.get_device_properties(i) + total_memory += gpu_properties.total_memory / (1024**3) # in GB + elif torch.xpu.is_available(): + gpu_count = torch.xpu.device_count() + total_memory = 0 + for i in range(gpu_count): + gpu_properties = torch.xpu.get_device_properties(i) + total_memory += gpu_properties.total_memory / (1024**3) + elif torch.backends.mps.is_available(): + gpu_count = 1 + total_memory = psutil.virtual_memory().available / (1024**3) + + avg_gpu_memory = total_memory / gpu_count + + # rough estimate of batch size + if batch_size_type == "frame": + batch_size_per_gpu = max(int(38400 * (avg_gpu_memory - 5) / 75), int(max_sample_length)) + elif batch_size_type == "sample": + batch_size_per_gpu = int(200 / (total_duration / total_samples)) + + if total_samples < 64: + max_samples = int(total_samples * 0.25) + + num_warmup_updates = max(num_warmup_updates, int(total_samples * 0.05)) + + # take 1.2M updates as the maximum + max_updates = 1200000 + + if batch_size_type == "frame": + mini_batch_duration = batch_size_per_gpu * gpu_count * hop_length / sampling_rate + updates_per_epoch = total_duration / mini_batch_duration + elif batch_size_type == "sample": + updates_per_epoch = total_samples / batch_size_per_gpu / gpu_count + + epochs = int(max_updates / updates_per_epoch) + + if finetune: + learning_rate = 1e-5 + else: + learning_rate = 7.5e-5 + + return ( + epochs, + learning_rate, + batch_size_per_gpu, + max_samples, + num_warmup_updates, + total_samples, + ) + + +def prune_checkpoint(checkpoint_path: str, new_checkpoint_path: str, save_ema: bool, safetensors: bool) -> str: + try: + checkpoint = torch.load(checkpoint_path, weights_only=True) + print("Original Checkpoint Keys:", checkpoint.keys()) + + to_retain = "ema_model_state_dict" if save_ema else "model_state_dict" + try: + model_state_dict_to_retain = checkpoint[to_retain] + except KeyError: + return f"{to_retain} not found in the checkpoint." + + if safetensors: + new_checkpoint_path = new_checkpoint_path.replace(".pt", ".safetensors") + save_file(model_state_dict_to_retain, new_checkpoint_path) + else: + new_checkpoint_path = new_checkpoint_path.replace(".safetensors", ".pt") + new_checkpoint = {"ema_model_state_dict": model_state_dict_to_retain} + torch.save(new_checkpoint, new_checkpoint_path) + + return f"New checkpoint saved at: {new_checkpoint_path}" + + except Exception as e: + return f"An error occurred: {e}" + + +def expand_model_embeddings(ckpt_path, new_ckpt_path, num_new_tokens=42): + seed = 666 + random.seed(seed) + os.environ["PYTHONHASHSEED"] = str(seed) + torch.manual_seed(seed) + torch.cuda.manual_seed(seed) + torch.cuda.manual_seed_all(seed) + torch.backends.cudnn.deterministic = True + torch.backends.cudnn.benchmark = False + + if ckpt_path.endswith(".safetensors"): + ckpt = load_file(ckpt_path, device="cpu") + ckpt = {"ema_model_state_dict": ckpt} + elif ckpt_path.endswith(".pt"): + ckpt = torch.load(ckpt_path, map_location="cpu") + + ema_sd = ckpt.get("ema_model_state_dict", {}) + embed_key_ema = "ema_model.transformer.text_embed.text_embed.weight" + old_embed_ema = ema_sd[embed_key_ema] + + vocab_old = old_embed_ema.size(0) + embed_dim = old_embed_ema.size(1) + vocab_new = vocab_old + num_new_tokens + + def expand_embeddings(old_embeddings): + new_embeddings = torch.zeros((vocab_new, embed_dim)) + new_embeddings[:vocab_old] = old_embeddings + new_embeddings[vocab_old:] = torch.randn((num_new_tokens, embed_dim)) + return new_embeddings + + ema_sd[embed_key_ema] = expand_embeddings(ema_sd[embed_key_ema]) + + if new_ckpt_path.endswith(".safetensors"): + save_file(ema_sd, new_ckpt_path) + elif new_ckpt_path.endswith(".pt"): + torch.save(ckpt, new_ckpt_path) + + return vocab_new + + +def vocab_count(text): + return str(len(text.split(","))) + + +def vocab_extend(project_name, symbols, model_type): + if symbols == "": + return "Symbols empty!" + + name_project = project_name + path_project = os.path.join(path_data, name_project) + file_vocab_project = os.path.join(path_project, "vocab.txt") + + file_vocab = os.path.join(path_data, "Emilia_ZH_EN_pinyin/vocab.txt") + if not os.path.isfile(file_vocab): + return f"the file {file_vocab} not found !" + + symbols = symbols.split(",") + if symbols == []: + return "Symbols to extend not found." + + with open(file_vocab, "r", encoding="utf-8-sig") as f: + data = f.read() + vocab = data.split("\n") + vocab_check = set(vocab) + + miss_symbols = [] + for item in symbols: + item = item.replace(" ", "") + if item in vocab_check: + continue + miss_symbols.append(item) + + if miss_symbols == []: + return "Symbols are okay no need to extend." + + size_vocab = len(vocab) + vocab.pop() + for item in miss_symbols: + vocab.append(item) + + vocab.append("") + + with open(file_vocab_project, "w", encoding="utf-8") as f: + f.write("\n".join(vocab)) + + if model_type == "F5TTS_v1_Base": + ckpt_path = str(cached_path("hf://SWivid/F5-TTS/F5TTS_v1_Base/model_1250000.safetensors")) + elif model_type == "F5TTS_Base": + ckpt_path = str(cached_path("hf://SWivid/F5-TTS/F5TTS_Base/model_1200000.pt")) + elif model_type == "E2TTS_Base": + ckpt_path = str(cached_path("hf://SWivid/E2-TTS/E2TTS_Base/model_1200000.pt")) + + vocab_size_new = len(miss_symbols) + + dataset_name = name_project.replace("_pinyin", "").replace("_char", "") + new_ckpt_path = os.path.join(path_project_ckpts, dataset_name) + os.makedirs(new_ckpt_path, exist_ok=True) + + # Add pretrained_ prefix to model when copying for consistency with finetune_cli.py + new_ckpt_file = os.path.join(new_ckpt_path, "pretrained_" + os.path.basename(ckpt_path)) + + size = expand_model_embeddings(ckpt_path, new_ckpt_file, num_new_tokens=vocab_size_new) + + vocab_new = "\n".join(miss_symbols) + return f"vocab old size : {size_vocab}\nvocab new size : {size}\nvocab add : {vocab_size_new}\nnew symbols :\n{vocab_new}" + + +def vocab_check(project_name, tokenizer_type): + name_project = project_name + path_project = os.path.join(path_data, name_project) + + file_metadata = os.path.join(path_project, "metadata.csv") + + file_vocab = os.path.join(path_data, "Emilia_ZH_EN_pinyin/vocab.txt") + if not os.path.isfile(file_vocab): + return f"the file {file_vocab} not found !", "" + + with open(file_vocab, "r", encoding="utf-8-sig") as f: + data = f.read() + vocab = data.split("\n") + vocab = set(vocab) + + if not os.path.isfile(file_metadata): + return f"the file {file_metadata} not found !", "" + + with open(file_metadata, "r", encoding="utf-8-sig") as f: + data = f.read() + + miss_symbols = [] + miss_symbols_keep = {} + for item in data.split("\n"): + sp = item.split("|") + if len(sp) != 2: + continue + + text = sp[1].strip() + if tokenizer_type == "pinyin": + text = convert_char_to_pinyin([text], polyphone=True)[0] + + for t in text: + if t not in vocab and t not in miss_symbols_keep: + miss_symbols.append(t) + miss_symbols_keep[t] = t + + if miss_symbols == []: + vocab_miss = "" + info = "You can train using your language !" + else: + vocab_miss = ",".join(miss_symbols) + info = f"The following {len(miss_symbols)} symbols are missing in your language\n\n" + + return info, vocab_miss + + +def get_random_sample_prepare(project_name): + name_project = project_name + path_project = os.path.join(path_data, name_project) + file_arrow = os.path.join(path_project, "raw.arrow") + if not os.path.isfile(file_arrow): + return "", None + dataset = Dataset_.from_file(file_arrow) + random_sample = dataset.shuffle(seed=random.randint(0, 1000)).select([0]) + text = "[" + " , ".join(["' " + t + " '" for t in random_sample["text"][0]]) + "]" + audio_path = random_sample["audio_path"][0] + return text, audio_path + + +def get_random_sample_transcribe(project_name): + name_project = project_name + path_project = os.path.join(path_data, name_project) + file_metadata = os.path.join(path_project, "metadata.csv") + if not os.path.isfile(file_metadata): + return "", None + + data = "" + with open(file_metadata, "r", encoding="utf-8-sig") as f: + data = f.read() + + list_data = [] + for item in data.split("\n"): + sp = item.split("|") + if len(sp) != 2: + continue + + # fixed audio when it is absolute + file_audio = get_correct_audio_path(sp[0], os.path.join(path_project, "wavs")) + list_data.append([file_audio, sp[1]]) + + if list_data == []: + return "", None + + random_item = random.choice(list_data) + + return random_item[1], random_item[0] + + +def get_random_sample_infer(project_name): + text, audio = get_random_sample_transcribe(project_name) + return ( + text, + text, + audio, + ) + + +def infer( + project, file_checkpoint, exp_name, ref_text, ref_audio, gen_text, nfe_step, use_ema, speed, seed, remove_silence +): + global last_checkpoint, last_device, tts_api, last_ema + + if not os.path.isfile(file_checkpoint): + return None, "checkpoint not found!" + + if training_process is not None: + device_test = "cpu" + else: + device_test = None + + if last_checkpoint != file_checkpoint or last_device != device_test or last_ema != use_ema or tts_api is None: + if last_checkpoint != file_checkpoint: + last_checkpoint = file_checkpoint + + if last_device != device_test: + last_device = device_test + + if last_ema != use_ema: + last_ema = use_ema + + vocab_file = os.path.join(path_data, project, "vocab.txt") + + tts_api = F5TTS( + model=exp_name, ckpt_file=file_checkpoint, vocab_file=vocab_file, device=device_test, use_ema=use_ema + ) + + print("update >> ", device_test, file_checkpoint, use_ema) + + if seed == -1: # -1 used for random + seed = None + + with tempfile.NamedTemporaryFile(delete=False, suffix=".wav") as f: + tts_api.infer( + ref_file=ref_audio, + ref_text=ref_text.strip(), + gen_text=gen_text.strip(), + nfe_step=nfe_step, + speed=speed, + remove_silence=remove_silence, + file_wave=f.name, + seed=seed, + ) + return f.name, tts_api.device, str(tts_api.seed) + + +def check_finetune(finetune): + return gr.update(interactive=finetune), gr.update(interactive=finetune), gr.update(interactive=finetune) + + +def get_checkpoints_project(project_name, is_gradio=True): + if project_name is None: + return [], "" + project_name = project_name.replace("_pinyin", "").replace("_char", "") + + if os.path.isdir(path_project_ckpts): + files_checkpoints = glob(os.path.join(path_project_ckpts, project_name, "*.pt")) + # Separate pretrained and regular checkpoints + pretrained_checkpoints = [f for f in files_checkpoints if "pretrained_" in os.path.basename(f)] + regular_checkpoints = [ + f + for f in files_checkpoints + if "pretrained_" not in os.path.basename(f) and "model_last.pt" not in os.path.basename(f) + ] + last_checkpoint = [f for f in files_checkpoints if "model_last.pt" in os.path.basename(f)] + + # Sort regular checkpoints by number + regular_checkpoints = sorted( + regular_checkpoints, key=lambda x: int(os.path.basename(x).split("_")[1].split(".")[0]) + ) + + # Combine in order: pretrained, regular, last + files_checkpoints = pretrained_checkpoints + regular_checkpoints + last_checkpoint + else: + files_checkpoints = [] + + selelect_checkpoint = None if not files_checkpoints else files_checkpoints[0] + + if is_gradio: + return gr.update(choices=files_checkpoints, value=selelect_checkpoint) + + return files_checkpoints, selelect_checkpoint + + +def get_audio_project(project_name, is_gradio=True): + if project_name is None: + return [], "" + project_name = project_name.replace("_pinyin", "").replace("_char", "") + + if os.path.isdir(path_project_ckpts): + files_audios = glob(os.path.join(path_project_ckpts, project_name, "samples", "*.wav")) + files_audios = sorted(files_audios, key=lambda x: int(os.path.basename(x).split("_")[1].split(".")[0])) + + files_audios = [item.replace("_gen.wav", "") for item in files_audios if item.endswith("_gen.wav")] + else: + files_audios = [] + + selelect_checkpoint = None if not files_audios else files_audios[0] + + if is_gradio: + return gr.update(choices=files_audios, value=selelect_checkpoint) + + return files_audios, selelect_checkpoint + + +def get_gpu_stats(): + gpu_stats = "" + + if torch.cuda.is_available(): + gpu_count = torch.cuda.device_count() + for i in range(gpu_count): + gpu_name = torch.cuda.get_device_name(i) + gpu_properties = torch.cuda.get_device_properties(i) + total_memory = gpu_properties.total_memory / (1024**3) # in GB + allocated_memory = torch.cuda.memory_allocated(i) / (1024**2) # in MB + reserved_memory = torch.cuda.memory_reserved(i) / (1024**2) # in MB + + gpu_stats += ( + f"GPU {i} Name: {gpu_name}\n" + f"Total GPU memory (GPU {i}): {total_memory:.2f} GB\n" + f"Allocated GPU memory (GPU {i}): {allocated_memory:.2f} MB\n" + f"Reserved GPU memory (GPU {i}): {reserved_memory:.2f} MB\n\n" + ) + elif torch.xpu.is_available(): + gpu_count = torch.xpu.device_count() + for i in range(gpu_count): + gpu_name = torch.xpu.get_device_name(i) + gpu_properties = torch.xpu.get_device_properties(i) + total_memory = gpu_properties.total_memory / (1024**3) # in GB + allocated_memory = torch.xpu.memory_allocated(i) / (1024**2) # in MB + reserved_memory = torch.xpu.memory_reserved(i) / (1024**2) # in MB + + gpu_stats += ( + f"GPU {i} Name: {gpu_name}\n" + f"Total GPU memory (GPU {i}): {total_memory:.2f} GB\n" + f"Allocated GPU memory (GPU {i}): {allocated_memory:.2f} MB\n" + f"Reserved GPU memory (GPU {i}): {reserved_memory:.2f} MB\n\n" + ) + elif torch.backends.mps.is_available(): + gpu_count = 1 + gpu_stats += "MPS GPU\n" + total_memory = psutil.virtual_memory().total / ( + 1024**3 + ) # Total system memory (MPS doesn't have its own memory) + allocated_memory = 0 + reserved_memory = 0 + + gpu_stats += ( + f"Total system memory: {total_memory:.2f} GB\n" + f"Allocated GPU memory (MPS): {allocated_memory:.2f} MB\n" + f"Reserved GPU memory (MPS): {reserved_memory:.2f} MB\n" + ) + + else: + gpu_stats = "No GPU available" + + return gpu_stats + + +def get_cpu_stats(): + cpu_usage = psutil.cpu_percent(interval=1) + memory_info = psutil.virtual_memory() + memory_used = memory_info.used / (1024**2) + memory_total = memory_info.total / (1024**2) + memory_percent = memory_info.percent + + pid = os.getpid() + process = psutil.Process(pid) + nice_value = process.nice() + + cpu_stats = ( + f"CPU Usage: {cpu_usage:.2f}%\n" + f"System Memory: {memory_used:.2f} MB used / {memory_total:.2f} MB total ({memory_percent}% used)\n" + f"Process Priority (Nice value): {nice_value}" + ) + + return cpu_stats + + +def get_combined_stats(): + gpu_stats = get_gpu_stats() + cpu_stats = get_cpu_stats() + combined_stats = f"### GPU Stats\n{gpu_stats}\n\n### CPU Stats\n{cpu_stats}" + return combined_stats + + +def get_audio_select(file_sample): + select_audio_ref = file_sample + select_audio_gen = file_sample + + if file_sample is not None: + select_audio_ref += "_ref.wav" + select_audio_gen += "_gen.wav" + + return select_audio_ref, select_audio_gen + + +with gr.Blocks() as app: + gr.Markdown( + """ +# F5 TTS Automatic Finetune + +This is a local web UI for F5 TTS finetuning support. This app supports the following TTS models: + +* [F5-TTS](https://arxiv.org/abs/2410.06885) (A Fairytaler that Fakes Fluent and Faithful Speech with Flow Matching) +* [E2 TTS](https://arxiv.org/abs/2406.18009) (Embarrassingly Easy Fully Non-Autoregressive Zero-Shot TTS) + +The pretrained checkpoints support English and Chinese. + +For tutorial and updates check here (https://github.com/SWivid/F5-TTS/discussions/143) +""" + ) + + with gr.Row(): + projects, projects_selelect = get_list_projects() + tokenizer_type = gr.Radio(label="Tokenizer Type", choices=["pinyin", "char", "custom"], value="pinyin") + project_name = gr.Textbox(label="Project Name", value="my_speak") + bt_create = gr.Button("Create a New Project") + + with gr.Row(): + cm_project = gr.Dropdown( + choices=projects, value=projects_selelect, label="Project", allow_custom_value=True, scale=6 + ) + ch_refresh_project = gr.Button("Refresh", scale=1) + + bt_create.click(fn=create_data_project, inputs=[project_name, tokenizer_type], outputs=[cm_project]) + + with gr.Tabs(): + with gr.TabItem("Transcribe Data"): + gr.Markdown("""```plaintext +Skip this step if you have your dataset, metadata.csv, and a folder wavs with all the audio files. +```""") + + ch_manual = gr.Checkbox(label="Audio from Path", value=False) + + mark_info_transcribe = gr.Markdown( + """```plaintext + Place your 'wavs' folder and 'metadata.csv' file in the '{your_project_name}' directory. + + my_speak/ + │ + └── dataset/ + ├── audio1.wav + └── audio2.wav + ... + ```""", + visible=False, + ) + + audio_speaker = gr.File(label="Voice", type="filepath", file_count="multiple") + txt_lang = gr.Textbox(label="Language", value="English") + bt_transcribe = bt_create = gr.Button("Transcribe") + txt_info_transcribe = gr.Textbox(label="Info", value="") + bt_transcribe.click( + fn=transcribe_all, + inputs=[cm_project, audio_speaker, txt_lang, ch_manual], + outputs=[txt_info_transcribe], + ) + ch_manual.change(fn=check_user, inputs=[ch_manual], outputs=[audio_speaker, mark_info_transcribe]) + + random_sample_transcribe = gr.Button("Random Sample") + + with gr.Row(): + random_text_transcribe = gr.Textbox(label="Text") + random_audio_transcribe = gr.Audio(label="Audio", type="filepath") + + random_sample_transcribe.click( + fn=get_random_sample_transcribe, + inputs=[cm_project], + outputs=[random_text_transcribe, random_audio_transcribe], + ) + + with gr.TabItem("Vocab Check"): + gr.Markdown("""```plaintext +Check the vocabulary for fine-tuning Emilia_ZH_EN to ensure all symbols are included. For fine-tuning a new language. +```""") + + check_button = gr.Button("Check Vocab") + txt_info_check = gr.Textbox(label="Info", value="") + + gr.Markdown("""```plaintext +Using the extended model, you can finetune to a new language that is missing symbols in the vocab. This creates a new model with a new vocabulary size and saves it in your ckpts/project folder. +```""") + + exp_name_extend = gr.Radio( + label="Model", choices=["F5TTS_v1_Base", "F5TTS_Base", "E2TTS_Base"], value="F5TTS_v1_Base" + ) + + with gr.Row(): + txt_extend = gr.Textbox( + label="Symbols", + value="", + placeholder="To add new symbols, make sure to use ',' for each symbol", + scale=6, + ) + txt_count_symbol = gr.Textbox(label="New Vocab Size", value="", scale=1) + + extend_button = gr.Button("Extend") + txt_info_extend = gr.Textbox(label="Info", value="") + + txt_extend.change(vocab_count, inputs=[txt_extend], outputs=[txt_count_symbol]) + check_button.click( + fn=vocab_check, inputs=[cm_project, tokenizer_type], outputs=[txt_info_check, txt_extend] + ) + extend_button.click( + fn=vocab_extend, inputs=[cm_project, txt_extend, exp_name_extend], outputs=[txt_info_extend] + ) + + with gr.TabItem("Prepare Data"): + gr.Markdown("""```plaintext +Skip this step if you have your dataset, raw.arrow, duration.json, and vocab.txt +```""") + + gr.Markdown( + """```plaintext + Place all your "wavs" folder and your "metadata.csv" file in your project name directory. + + Supported audio formats: "wav", "mp3", "aac", "flac", "m4a", "alac", "ogg", "aiff", "wma", "amr" + + Example wav format: + my_speak/ + │ + ├── wavs/ + │ ├── audio1.wav + │ └── audio2.wav + | ... + │ + └── metadata.csv + + File format metadata.csv: + + audio1|text1 or audio1.wav|text1 or your_path/audio1.wav|text1 + audio2|text1 or audio2.wav|text1 or your_path/audio2.wav|text1 + ... + + ```""" + ) + ch_tokenizern = gr.Checkbox(label="Create Vocabulary", value=False, visible=False) + + bt_prepare = bt_create = gr.Button("Prepare") + txt_info_prepare = gr.Textbox(label="Info", value="") + txt_vocab_prepare = gr.Textbox(label="Vocab", value="") + + bt_prepare.click( + fn=create_metadata, inputs=[cm_project, ch_tokenizern], outputs=[txt_info_prepare, txt_vocab_prepare] + ) + + random_sample_prepare = gr.Button("Random Sample") + + with gr.Row(): + random_text_prepare = gr.Textbox(label="Tokenizer") + random_audio_prepare = gr.Audio(label="Audio", type="filepath") + + random_sample_prepare.click( + fn=get_random_sample_prepare, inputs=[cm_project], outputs=[random_text_prepare, random_audio_prepare] + ) + + with gr.TabItem("Train Model"): + gr.Markdown("""```plaintext +The auto-setting is still experimental. Set a large value of epoch if not sure; and keep last N checkpoints if limited disk space. +If you encounter a memory error, try reducing the batch size per GPU to a smaller number. +```""") + with gr.Row(): + exp_name = gr.Radio(label="Model", choices=["F5TTS_v1_Base", "F5TTS_Base", "E2TTS_Base"]) + tokenizer_file = gr.Textbox(label="Tokenizer File") + file_checkpoint_train = gr.Textbox(label="Path to the Pretrained Checkpoint") + + with gr.Row(): + ch_finetune = bt_create = gr.Checkbox(label="Finetune") + lb_samples = gr.Label(label="Samples") + bt_calculate = bt_create = gr.Button("Auto Settings") + + with gr.Row(): + epochs = gr.Number(label="Epochs") + learning_rate = gr.Number(label="Learning Rate", step=0.5e-5) + max_grad_norm = gr.Number(label="Max Gradient Norm") + num_warmup_updates = gr.Number(label="Warmup Updates") + + with gr.Row(): + batch_size_type = gr.Radio( + label="Batch Size Type", + choices=["frame", "sample"], + info="frame is calculated as seconds * sampling_rate / hop_length", + ) + batch_size_per_gpu = gr.Number(label="Batch Size per GPU", info="N frames or N samples") + grad_accumulation_steps = gr.Number( + label="Gradient Accumulation Steps", info="Effective batch size is multiplied by this value" + ) + max_samples = gr.Number(label="Max Samples", info="Maximum number of samples per single GPU batch") + + with gr.Row(): + save_per_updates = gr.Number( + label="Save per Updates", + info="Save intermediate checkpoints every N updates", + minimum=10, + ) + keep_last_n_checkpoints = gr.Number( + label="Keep Last N Checkpoints", + step=1, + precision=0, + info="-1 to keep all, 0 to not save intermediate, > 0 to keep last N", + minimum=-1, + ) + last_per_updates = gr.Number( + label="Last per Updates", + info="Save latest checkpoint with suffix _last.pt every N updates", + minimum=10, + ) + gr.Radio(label="") # placeholder + + with gr.Row(): + ch_8bit_adam = gr.Checkbox(label="Use 8-bit Adam optimizer") + mixed_precision = gr.Radio(label="Mixed Precision", choices=["none", "fp16", "bf16"]) + cd_logger = gr.Radio(label="Logger", choices=["none", "wandb", "tensorboard"]) + with gr.Column(): + start_button = gr.Button("Start Training") + stop_button = gr.Button("Stop Training", interactive=False) + + if projects_selelect is not None: + ( + exp_name_value, + learning_rate_value, + batch_size_per_gpu_value, + batch_size_type_value, + max_samples_value, + grad_accumulation_steps_value, + max_grad_norm_value, + epochs_value, + num_warmup_updates_value, + save_per_updates_value, + keep_last_n_checkpoints_value, + last_per_updates_value, + finetune_value, + file_checkpoint_train_value, + tokenizer_type_value, + tokenizer_file_value, + mixed_precision_value, + logger_value, + bnb_optimizer_value, + ) = load_settings(projects_selelect) + + # Assigning values to the respective components + exp_name.value = exp_name_value + learning_rate.value = learning_rate_value + batch_size_per_gpu.value = batch_size_per_gpu_value + batch_size_type.value = batch_size_type_value + max_samples.value = max_samples_value + grad_accumulation_steps.value = grad_accumulation_steps_value + max_grad_norm.value = max_grad_norm_value + epochs.value = epochs_value + num_warmup_updates.value = num_warmup_updates_value + save_per_updates.value = save_per_updates_value + keep_last_n_checkpoints.value = keep_last_n_checkpoints_value + last_per_updates.value = last_per_updates_value + ch_finetune.value = finetune_value + file_checkpoint_train.value = file_checkpoint_train_value + tokenizer_type.value = tokenizer_type_value + tokenizer_file.value = tokenizer_file_value + mixed_precision.value = mixed_precision_value + cd_logger.value = logger_value + ch_8bit_adam.value = bnb_optimizer_value + + ch_stream = gr.Checkbox(label="Stream Output Experiment", value=True) + txt_info_train = gr.Textbox(label="Info", value="") + + list_audios, select_audio = get_audio_project(projects_selelect, False) + + select_audio_ref = select_audio + select_audio_gen = select_audio + + if select_audio is not None: + select_audio_ref += "_ref.wav" + select_audio_gen += "_gen.wav" + + with gr.Row(): + ch_list_audio = gr.Dropdown( + choices=list_audios, + value=select_audio, + label="Audios", + allow_custom_value=True, + scale=6, + interactive=True, + ) + bt_stream_audio = gr.Button("Refresh", scale=1) + bt_stream_audio.click(fn=get_audio_project, inputs=[cm_project], outputs=[ch_list_audio]) + cm_project.change(fn=get_audio_project, inputs=[cm_project], outputs=[ch_list_audio]) + + with gr.Row(): + audio_ref_stream = gr.Audio(label="Original", type="filepath", value=select_audio_ref) + audio_gen_stream = gr.Audio(label="Generate", type="filepath", value=select_audio_gen) + + ch_list_audio.change( + fn=get_audio_select, + inputs=[ch_list_audio], + outputs=[audio_ref_stream, audio_gen_stream], + ) + + start_button.click( + fn=start_training, + inputs=[ + cm_project, + exp_name, + learning_rate, + batch_size_per_gpu, + batch_size_type, + max_samples, + grad_accumulation_steps, + max_grad_norm, + epochs, + num_warmup_updates, + save_per_updates, + keep_last_n_checkpoints, + last_per_updates, + ch_finetune, + file_checkpoint_train, + tokenizer_type, + tokenizer_file, + mixed_precision, + ch_stream, + cd_logger, + ch_8bit_adam, + ], + outputs=[txt_info_train, start_button, stop_button], + ) + stop_button.click(fn=stop_training, outputs=[txt_info_train, start_button, stop_button]) + + bt_calculate.click( + fn=calculate_train, + inputs=[ + cm_project, + epochs, + learning_rate, + batch_size_per_gpu, + batch_size_type, + max_samples, + num_warmup_updates, + ch_finetune, + ], + outputs=[ + epochs, + learning_rate, + batch_size_per_gpu, + max_samples, + num_warmup_updates, + lb_samples, + ], + ) + + ch_finetune.change( + check_finetune, inputs=[ch_finetune], outputs=[file_checkpoint_train, tokenizer_file, tokenizer_type] + ) + + def setup_load_settings(): + output_components = [ + exp_name, + learning_rate, + batch_size_per_gpu, + batch_size_type, + max_samples, + grad_accumulation_steps, + max_grad_norm, + epochs, + num_warmup_updates, + save_per_updates, + keep_last_n_checkpoints, + last_per_updates, + ch_finetune, + file_checkpoint_train, + tokenizer_type, + tokenizer_file, + mixed_precision, + cd_logger, + ch_8bit_adam, + ] + return output_components + + outputs = setup_load_settings() + + cm_project.change( + fn=load_settings, + inputs=[cm_project], + outputs=outputs, + ) + + ch_refresh_project.click( + fn=load_settings, + inputs=[cm_project], + outputs=outputs, + ) + + with gr.TabItem("Test Model"): + gr.Markdown("""```plaintext +Check the use_ema setting (True or False) for your model to see what works best for you. Set seed to -1 for random. +```""") + exp_name = gr.Radio( + label="Model", choices=["F5TTS_v1_Base", "F5TTS_Base", "E2TTS_Base"], value="F5TTS_v1_Base" + ) + list_checkpoints, checkpoint_select = get_checkpoints_project(projects_selelect, False) + + with gr.Row(): + nfe_step = gr.Number(label="NFE Step", value=32) + speed = gr.Slider(label="Speed", value=1.0, minimum=0.3, maximum=2.0, step=0.1) + seed = gr.Number(label="Random Seed", value=-1, minimum=-1) + remove_silence = gr.Checkbox(label="Remove Silence") + + with gr.Row(): + ch_use_ema = gr.Checkbox( + label="Use EMA", value=True, info="Turn off at early stage might offer better results" + ) + cm_checkpoint = gr.Dropdown( + choices=list_checkpoints, value=checkpoint_select, label="Checkpoints", allow_custom_value=True + ) + bt_checkpoint_refresh = gr.Button("Refresh") + + random_sample_infer = gr.Button("Random Sample") + + ref_text = gr.Textbox(label="Reference Text") + ref_audio = gr.Audio(label="Reference Audio", type="filepath") + gen_text = gr.Textbox(label="Text to Generate") + + random_sample_infer.click( + fn=get_random_sample_infer, inputs=[cm_project], outputs=[ref_text, gen_text, ref_audio] + ) + + with gr.Row(): + txt_info_gpu = gr.Textbox("", label="Inference on Device :") + seed_info = gr.Textbox(label="Used Random Seed :") + check_button_infer = gr.Button("Inference") + + gen_audio = gr.Audio(label="Generated Audio", type="filepath") + + check_button_infer.click( + fn=infer, + inputs=[ + cm_project, + cm_checkpoint, + exp_name, + ref_text, + ref_audio, + gen_text, + nfe_step, + ch_use_ema, + speed, + seed, + remove_silence, + ], + outputs=[gen_audio, txt_info_gpu, seed_info], + ) + + bt_checkpoint_refresh.click(fn=get_checkpoints_project, inputs=[cm_project], outputs=[cm_checkpoint]) + cm_project.change(fn=get_checkpoints_project, inputs=[cm_project], outputs=[cm_checkpoint]) + + with gr.TabItem("Prune Checkpoint"): + gr.Markdown("""```plaintext +Reduce the Base model size from 5GB to 1.3GB. The new checkpoint file prunes out optimizer and etc., can be used for inference or finetuning afterward, but not able to resume pretraining. +```""") + txt_path_checkpoint = gr.Textbox(label="Path to Checkpoint:") + txt_path_checkpoint_small = gr.Textbox(label="Path to Output:") + with gr.Row(): + ch_save_ema = gr.Checkbox(label="Save EMA checkpoint", value=True) + ch_safetensors = gr.Checkbox(label="Save with safetensors format", value=True) + txt_info_reduse = gr.Textbox(label="Info", value="") + reduse_button = gr.Button("Prune") + reduse_button.click( + fn=prune_checkpoint, + inputs=[txt_path_checkpoint, txt_path_checkpoint_small, ch_save_ema, ch_safetensors], + outputs=[txt_info_reduse], + ) + + with gr.TabItem("System Info"): + output_box = gr.Textbox(label="GPU and CPU Information", lines=20) + + def update_stats(): + return get_combined_stats() + + update_button = gr.Button("Update Stats") + update_button.click(fn=update_stats, outputs=output_box) + + def auto_update(): + yield gr.update(value=update_stats()) + + gr.update(fn=auto_update, inputs=[], outputs=output_box) + + +@click.command() +@click.option("--port", "-p", default=None, type=int, help="Port to run the app on") +@click.option("--host", "-H", default=None, help="Host to run the app on") +@click.option( + "--share", + "-s", + default=False, + is_flag=True, + help="Share the app via Gradio share link", +) +@click.option("--api", "-a", default=True, is_flag=True, help="Allow API access") +def main(port, host, share, api): + global app + print("Starting app...") + app.queue(api_open=api).launch(server_name=host, server_port=port, share=share, show_api=api) + + +if __name__ == "__main__": + main() diff --git a/src/f5_tts/src/f5_tts/train/train.py b/src/f5_tts/src/f5_tts/train/train.py new file mode 100644 index 0000000000000000000000000000000000000000..ffea91e74de30985b3ccda45e2734d1179d013d8 --- /dev/null +++ b/src/f5_tts/src/f5_tts/train/train.py @@ -0,0 +1,77 @@ +# training script. + +import os +from importlib.resources import files + +import hydra +from omegaconf import OmegaConf + +from f5_tts.model import CFM, Trainer +from f5_tts.model.dataset import load_dataset +from f5_tts.model.utils import get_tokenizer + + +os.chdir(str(files("f5_tts").joinpath("../.."))) # change working directory to root of project (local editable) + + +@hydra.main(version_base="1.3", config_path=str(files("f5_tts").joinpath("configs")), config_name=None) +def main(model_cfg): + model_cls = hydra.utils.get_class(f"f5_tts.model.{model_cfg.model.backbone}") + model_arc = model_cfg.model.arch + tokenizer = model_cfg.model.tokenizer + mel_spec_type = model_cfg.model.mel_spec.mel_spec_type + + exp_name = f"{model_cfg.model.name}_{mel_spec_type}_{model_cfg.model.tokenizer}_{model_cfg.datasets.name}" + wandb_resume_id = None + + # set text tokenizer + if tokenizer != "custom": + tokenizer_path = model_cfg.datasets.name + else: + tokenizer_path = model_cfg.model.tokenizer_path + vocab_char_map, vocab_size = get_tokenizer(tokenizer_path, tokenizer) + + # set model + model = CFM( + transformer=model_cls(**model_arc, text_num_embeds=vocab_size, mel_dim=model_cfg.model.mel_spec.n_mel_channels), + mel_spec_kwargs=model_cfg.model.mel_spec, + vocab_char_map=vocab_char_map, + ) + + # init trainer + trainer = Trainer( + model, + epochs=model_cfg.optim.epochs, + learning_rate=model_cfg.optim.learning_rate, + num_warmup_updates=model_cfg.optim.num_warmup_updates, + save_per_updates=model_cfg.ckpts.save_per_updates, + keep_last_n_checkpoints=model_cfg.ckpts.keep_last_n_checkpoints, + checkpoint_path=str(files("f5_tts").joinpath(f"../../{model_cfg.ckpts.save_dir}")), + batch_size_per_gpu=model_cfg.datasets.batch_size_per_gpu, + batch_size_type=model_cfg.datasets.batch_size_type, + max_samples=model_cfg.datasets.max_samples, + grad_accumulation_steps=model_cfg.optim.grad_accumulation_steps, + max_grad_norm=model_cfg.optim.max_grad_norm, + logger=model_cfg.ckpts.logger, + wandb_project="CFM-TTS", + wandb_run_name=exp_name, + wandb_resume_id=wandb_resume_id, + last_per_updates=model_cfg.ckpts.last_per_updates, + log_samples=model_cfg.ckpts.log_samples, + bnb_optimizer=model_cfg.optim.bnb_optimizer, + mel_spec_type=mel_spec_type, + is_local_vocoder=model_cfg.model.vocoder.is_local, + local_vocoder_path=model_cfg.model.vocoder.local_path, + model_cfg_dict=OmegaConf.to_container(model_cfg, resolve=True), + ) + + train_dataset = load_dataset(model_cfg.datasets.name, tokenizer, mel_spec_kwargs=model_cfg.model.mel_spec) + trainer.train( + train_dataset, + num_workers=model_cfg.datasets.num_workers, + resumable_with_seed=666, # seed for shuffling dataset + ) + + +if __name__ == "__main__": + main() diff --git a/src/third_party/BigVGAN/.gitignore b/src/third_party/BigVGAN/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..d95205c2340b913be8b1afe2b1a1f34db6a14717 --- /dev/null +++ b/src/third_party/BigVGAN/.gitignore @@ -0,0 +1,146 @@ +# BigVGAN +alias_free_activation/cuda/build/ +exp/ +tmp/ + +# Symlinks for bundled LibriTTS filelists +filelists/LibriTTS/train-clean-100 +filelists/LibriTTS/train-clean-360 +filelists/LibriTTS/train-other-500 +filelists/LibriTTS/dev-clean +filelists/LibriTTS/dev-other +filelists/LibriTTS/test-clean +filelists/LibriTTS/test-other + +# VSCode configs +.vscode/ + +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +.idea/ \ No newline at end of file diff --git a/src/third_party/BigVGAN/LICENSE b/src/third_party/BigVGAN/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..6016317a8d20a1d7c01bdd34451b424bf212e8c7 --- /dev/null +++ b/src/third_party/BigVGAN/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 NVIDIA CORPORATION. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/src/third_party/BigVGAN/README.md b/src/third_party/BigVGAN/README.md new file mode 100644 index 0000000000000000000000000000000000000000..c200b5536b4b1fb53238b1de6d7c088f51238ffe --- /dev/null +++ b/src/third_party/BigVGAN/README.md @@ -0,0 +1,266 @@ +## BigVGAN: A Universal Neural Vocoder with Large-Scale Training + +#### Sang-gil Lee, Wei Ping, Boris Ginsburg, Bryan Catanzaro, Sungroh Yoon + +[[Paper]](https://arxiv.org/abs/2206.04658) - [[Code]](https://github.com/NVIDIA/BigVGAN) - [[Showcase]](https://bigvgan-demo.github.io/) - [[Project Page]](https://research.nvidia.com/labs/adlr/projects/bigvgan/) - [[Weights]](https://huggingface.co/collections/nvidia/bigvgan-66959df3d97fd7d98d97dc9a) - [[Demo]](https://huggingface.co/spaces/nvidia/BigVGAN) + +[![PWC](https://img.shields.io/endpoint.svg?url=https://paperswithcode.com/badge/bigvgan-a-universal-neural-vocoder-with-large/speech-synthesis-on-libritts)](https://paperswithcode.com/sota/speech-synthesis-on-libritts?p=bigvgan-a-universal-neural-vocoder-with-large) + +
+ +## News +- **Sep 2024 (v2.4):** + - We have updated the pretrained checkpoints trained for 5M steps. This is final release of the BigVGAN-v2 checkpoints. + +- **Jul 2024 (v2.3):** + - General refactor and code improvements for improved readability. + - Fully fused CUDA kernel of anti-alised activation (upsampling + activation + downsampling) with inference speed benchmark. + +- **Jul 2024 (v2.2):** The repository now includes an interactive local demo using gradio. + +- **Jul 2024 (v2.1):** BigVGAN is now integrated with 🤗 Hugging Face Hub with easy access to inference using pretrained checkpoints. We also provide an interactive demo on Hugging Face Spaces. + +- **Jul 2024 (v2):** We release BigVGAN-v2 along with pretrained checkpoints. Below are the highlights: + - Custom CUDA kernel for inference: we provide a fused upsampling + activation kernel written in CUDA for accelerated inference speed. Our test shows 1.5 - 3x faster speed on a single A100 GPU. + - Improved discriminator and loss: BigVGAN-v2 is trained using a [multi-scale sub-band CQT discriminator](https://arxiv.org/abs/2311.14957) and a [multi-scale mel spectrogram loss](https://arxiv.org/abs/2306.06546). + - Larger training data: BigVGAN-v2 is trained using datasets containing diverse audio types, including speech in multiple languages, environmental sounds, and instruments. + - We provide pretrained checkpoints of BigVGAN-v2 using diverse audio configurations, supporting up to 44 kHz sampling rate and 512x upsampling ratio. + +## Installation + +The codebase has been tested on Python `3.10` and PyTorch `2.3.1` conda packages with either `pytorch-cuda=12.1` or `pytorch-cuda=11.8`. Below is an example command to create the conda environment: + +```shell +conda create -n bigvgan python=3.10 pytorch torchvision torchaudio pytorch-cuda=12.1 -c pytorch -c nvidia +conda activate bigvgan +``` + +Clone the repository and install dependencies: + +```shell +git clone https://github.com/NVIDIA/BigVGAN +cd BigVGAN +pip install -r requirements.txt +``` + +## Inference Quickstart using 🤗 Hugging Face Hub + +Below example describes how you can use BigVGAN: load the pretrained BigVGAN generator from Hugging Face Hub, compute mel spectrogram from input waveform, and generate synthesized waveform using the mel spectrogram as the model's input. + +```python +device = 'cuda' + +import torch +import bigvgan +import librosa +from meldataset import get_mel_spectrogram + +# instantiate the model. You can optionally set use_cuda_kernel=True for faster inference. +model = bigvgan.BigVGAN.from_pretrained('nvidia/bigvgan_v2_24khz_100band_256x', use_cuda_kernel=False) + +# remove weight norm in the model and set to eval mode +model.remove_weight_norm() +model = model.eval().to(device) + +# load wav file and compute mel spectrogram +wav_path = '/path/to/your/audio.wav' +wav, sr = librosa.load(wav_path, sr=model.h.sampling_rate, mono=True) # wav is np.ndarray with shape [T_time] and values in [-1, 1] +wav = torch.FloatTensor(wav).unsqueeze(0) # wav is FloatTensor with shape [B(1), T_time] + +# compute mel spectrogram from the ground truth audio +mel = get_mel_spectrogram(wav, model.h).to(device) # mel is FloatTensor with shape [B(1), C_mel, T_frame] + +# generate waveform from mel +with torch.inference_mode(): + wav_gen = model(mel) # wav_gen is FloatTensor with shape [B(1), 1, T_time] and values in [-1, 1] +wav_gen_float = wav_gen.squeeze(0).cpu() # wav_gen is FloatTensor with shape [1, T_time] + +# you can convert the generated waveform to 16 bit linear PCM +wav_gen_int16 = (wav_gen_float * 32767.0).numpy().astype('int16') # wav_gen is now np.ndarray with shape [1, T_time] and int16 dtype +``` + +## Local gradio demo + +You can run a local gradio demo using below command: + +```python +pip install -r demo/requirements.txt +python demo/app.py +``` + +## Training + +Create symbolic link to the root of the dataset. The codebase uses filelist with the relative path from the dataset. Below are the example commands for LibriTTS dataset: + +```shell +cd filelists/LibriTTS && \ +ln -s /path/to/your/LibriTTS/train-clean-100 train-clean-100 && \ +ln -s /path/to/your/LibriTTS/train-clean-360 train-clean-360 && \ +ln -s /path/to/your/LibriTTS/train-other-500 train-other-500 && \ +ln -s /path/to/your/LibriTTS/dev-clean dev-clean && \ +ln -s /path/to/your/LibriTTS/dev-other dev-other && \ +ln -s /path/to/your/LibriTTS/test-clean test-clean && \ +ln -s /path/to/your/LibriTTS/test-other test-other && \ +cd ../.. +``` + +Train BigVGAN model. Below is an example command for training BigVGAN-v2 using LibriTTS dataset at 24kHz with a full 100-band mel spectrogram as input: + +```shell +python train.py \ +--config configs/bigvgan_v2_24khz_100band_256x.json \ +--input_wavs_dir filelists/LibriTTS \ +--input_training_file filelists/LibriTTS/train-full.txt \ +--input_validation_file filelists/LibriTTS/val-full.txt \ +--list_input_unseen_wavs_dir filelists/LibriTTS filelists/LibriTTS \ +--list_input_unseen_validation_file filelists/LibriTTS/dev-clean.txt filelists/LibriTTS/dev-other.txt \ +--checkpoint_path exp/bigvgan_v2_24khz_100band_256x +``` + +## Synthesis + +Synthesize from BigVGAN model. Below is an example command for generating audio from the model. +It computes mel spectrograms using wav files from `--input_wavs_dir` and saves the generated audio to `--output_dir`. + +```shell +python inference.py \ +--checkpoint_file /path/to/your/bigvgan_v2_24khz_100band_256x/bigvgan_generator.pt \ +--input_wavs_dir /path/to/your/input_wav \ +--output_dir /path/to/your/output_wav +``` + +`inference_e2e.py` supports synthesis directly from the mel spectrogram saved in `.npy` format, with shapes `[1, channel, frame]` or `[channel, frame]`. +It loads mel spectrograms from `--input_mels_dir` and saves the generated audio to `--output_dir`. + +Make sure that the STFT hyperparameters for mel spectrogram are the same as the model, which are defined in `config.json` of the corresponding model. + +```shell +python inference_e2e.py \ +--checkpoint_file /path/to/your/bigvgan_v2_24khz_100band_256x/bigvgan_generator.pt \ +--input_mels_dir /path/to/your/input_mel \ +--output_dir /path/to/your/output_wav +``` + +## Using Custom CUDA Kernel for Synthesis + +You can apply the fast CUDA inference kernel by using a parameter `use_cuda_kernel` when instantiating BigVGAN: + +```python +generator = BigVGAN(h, use_cuda_kernel=True) +``` + +You can also pass `--use_cuda_kernel` to `inference.py` and `inference_e2e.py` to enable this feature. + +When applied for the first time, it builds the kernel using `nvcc` and `ninja`. If the build succeeds, the kernel is saved to `alias_free_activation/cuda/build` and the model automatically loads the kernel. The codebase has been tested using CUDA `12.1`. + +Please make sure that both are installed in your system and `nvcc` installed in your system matches the version your PyTorch build is using. + +We recommend running `test_cuda_vs_torch_model.py` first to build and check the correctness of the CUDA kernel. See below example command and its output, where it returns `[Success] test CUDA fused vs. plain torch BigVGAN inference`: + +```python +python tests/test_cuda_vs_torch_model.py \ +--checkpoint_file /path/to/your/bigvgan_generator.pt +``` + +```shell +loading plain Pytorch BigVGAN +... +loading CUDA kernel BigVGAN with auto-build +Detected CUDA files, patching ldflags +Emitting ninja build file /path/to/your/BigVGAN/alias_free_activation/cuda/build/build.ninja.. +Building extension module anti_alias_activation_cuda... +... +Loading extension module anti_alias_activation_cuda... +... +Loading '/path/to/your/bigvgan_generator.pt' +... +[Success] test CUDA fused vs. plain torch BigVGAN inference + > mean_difference=0.0007238413265440613 +... +``` + +If you see `[Fail] test CUDA fused vs. plain torch BigVGAN inference`, it means that the CUDA kernel inference is incorrect. Please check if `nvcc` installed in your system is compatible with your PyTorch version. + +## Pretrained Models + +We provide the [pretrained models on Hugging Face Collections](https://huggingface.co/collections/nvidia/bigvgan-66959df3d97fd7d98d97dc9a). +One can download the checkpoints of the generator weight (named `bigvgan_generator.pt`) and its discriminator/optimizer states (named `bigvgan_discriminator_optimizer.pt`) within the listed model repositories. + +| Model Name | Sampling Rate | Mel band | fmax | Upsampling Ratio | Params | Dataset | Steps | Fine-Tuned | +|:--------------------------------------------------------------------------------------------------------:|:-------------:|:--------:|:-----:|:----------------:|:------:|:--------------------------:|:-----:|:----------:| +| [bigvgan_v2_44khz_128band_512x](https://huggingface.co/nvidia/bigvgan_v2_44khz_128band_512x) | 44 kHz | 128 | 22050 | 512 | 122M | Large-scale Compilation | 5M | No | +| [bigvgan_v2_44khz_128band_256x](https://huggingface.co/nvidia/bigvgan_v2_44khz_128band_256x) | 44 kHz | 128 | 22050 | 256 | 112M | Large-scale Compilation | 5M | No | +| [bigvgan_v2_24khz_100band_256x](https://huggingface.co/nvidia/bigvgan_v2_24khz_100band_256x) | 24 kHz | 100 | 12000 | 256 | 112M | Large-scale Compilation | 5M | No | +| [bigvgan_v2_22khz_80band_256x](https://huggingface.co/nvidia/bigvgan_v2_22khz_80band_256x) | 22 kHz | 80 | 11025 | 256 | 112M | Large-scale Compilation | 5M | No | +| [bigvgan_v2_22khz_80band_fmax8k_256x](https://huggingface.co/nvidia/bigvgan_v2_22khz_80band_fmax8k_256x) | 22 kHz | 80 | 8000 | 256 | 112M | Large-scale Compilation | 5M | No | +| [bigvgan_24khz_100band](https://huggingface.co/nvidia/bigvgan_24khz_100band) | 24 kHz | 100 | 12000 | 256 | 112M | LibriTTS | 5M | No | +| [bigvgan_base_24khz_100band](https://huggingface.co/nvidia/bigvgan_base_24khz_100band) | 24 kHz | 100 | 12000 | 256 | 14M | LibriTTS | 5M | No | +| [bigvgan_22khz_80band](https://huggingface.co/nvidia/bigvgan_22khz_80band) | 22 kHz | 80 | 8000 | 256 | 112M | LibriTTS + VCTK + LJSpeech | 5M | No | +| [bigvgan_base_22khz_80band](https://huggingface.co/nvidia/bigvgan_base_22khz_80band) | 22 kHz | 80 | 8000 | 256 | 14M | LibriTTS + VCTK + LJSpeech | 5M | No | + +The paper results are based on the original 24kHz BigVGAN models (`bigvgan_24khz_100band` and `bigvgan_base_24khz_100band`) trained on LibriTTS dataset. +We also provide 22kHz BigVGAN models with band-limited setup (i.e., fmax=8000) for TTS applications. +Note that the checkpoints use `snakebeta` activation with log scale parameterization, which have the best overall quality. + +You can fine-tune the models by: + +1. downloading the checkpoints (both the generator weight and its discriminator/optimizer states) +2. resuming training using your audio dataset by specifying `--checkpoint_path` that includes the checkpoints when launching `train.py` + +## Training Details of BigVGAN-v2 + +Comapred to the original BigVGAN, the pretrained checkpoints of BigVGAN-v2 used `batch_size=32` with a longer `segment_size=65536` and are trained using 8 A100 GPUs. + +Note that the BigVGAN-v2 `json` config files in `./configs` use `batch_size=4` as default to fit in a single A100 GPU for training. You can fine-tune the models adjusting `batch_size` depending on your GPUs. + +When training BigVGAN-v2 from scratch with small batch size, it can potentially encounter the early divergence problem mentioned in the paper. In such case, we recommend lowering the `clip_grad_norm` value (e.g. `100`) for the early training iterations (e.g. 20000 steps) and increase the value to the default `500`. + +## Evaluation Results of BigVGAN-v2 + +Below are the objective results of the 24kHz model (`bigvgan_v2_24khz_100band_256x`) obtained from the LibriTTS `dev` sets. BigVGAN-v2 shows noticeable improvements of the metrics. The model also exhibits reduced perceptual artifacts, especially for non-speech audio. + +| Model | Dataset | Steps | PESQ(↑) | M-STFT(↓) | MCD(↓) | Periodicity(↓) | V/UV F1(↑) | +|:----------:|:-----------------------:|:-----:|:---------:|:----------:|:----------:|:--------------:|:----------:| +| BigVGAN | LibriTTS | 1M | 4.027 | 0.7997 | 0.3745 | 0.1018 | 0.9598 | +| BigVGAN | LibriTTS | 5M | 4.256 | 0.7409 | 0.2988 | 0.0809 | 0.9698 | +| BigVGAN-v2 | Large-scale Compilation | 3M | 4.359 | 0.7134 | 0.3060 | 0.0621 | 0.9777 | +| BigVGAN-v2 | Large-scale Compilation | 5M | **4.362** | **0.7026** | **0.2903** | **0.0593** | **0.9793** | + +## Speed Benchmark + +Below are the speed and VRAM usage benchmark results of BigVGAN from `tests/test_cuda_vs_torch_model.py`, using `bigvgan_v2_24khz_100band_256x` as a reference model. + +| GPU | num_mel_frame | use_cuda_kernel | Speed (kHz) | Real-time Factor | VRAM (GB) | +|:--------------------------:|:-------------:|:---------------:|:-----------:|:----------------:|:---------:| +| NVIDIA A100 | 256 | False | 1672.1 | 69.7x | 1.3 | +| | | True | 3916.5 | 163.2x | 1.3 | +| | 2048 | False | 1899.6 | 79.2x | 1.7 | +| | | True | 5330.1 | 222.1x | 1.7 | +| | 16384 | False | 1973.8 | 82.2x | 5.0 | +| | | True | 5761.7 | 240.1x | 4.4 | +| NVIDIA GeForce RTX 3080 | 256 | False | 841.1 | 35.0x | 1.3 | +| | | True | 1598.1 | 66.6x | 1.3 | +| | 2048 | False | 929.9 | 38.7x | 1.7 | +| | | True | 1971.3 | 82.1x | 1.6 | +| | 16384 | False | 943.4 | 39.3x | 5.0 | +| | | True | 2026.5 | 84.4x | 3.9 | +| NVIDIA GeForce RTX 2080 Ti | 256 | False | 515.6 | 21.5x | 1.3 | +| | | True | 811.3 | 33.8x | 1.3 | +| | 2048 | False | 576.5 | 24.0x | 1.7 | +| | | True | 1023.0 | 42.6x | 1.5 | +| | 16384 | False | 589.4 | 24.6x | 5.0 | +| | | True | 1068.1 | 44.5x | 3.2 | + +## Acknowledgements + +We thank Vijay Anand Korthikanti and Kevin J. Shih for their generous support in implementing the CUDA kernel for inference. + +## References + +- [HiFi-GAN](https://github.com/jik876/hifi-gan) (for generator and multi-period discriminator) +- [Snake](https://github.com/EdwardDixon/snake) (for periodic activation) +- [Alias-free-torch](https://github.com/junjun3518/alias-free-torch) (for anti-aliasing) +- [Julius](https://github.com/adefossez/julius) (for low-pass filter) +- [UnivNet](https://github.com/mindslab-ai/univnet) (for multi-resolution discriminator) +- [descript-audio-codec](https://github.com/descriptinc/descript-audio-codec) and [vocos](https://github.com/gemelo-ai/vocos) (for multi-band multi-scale STFT discriminator and multi-scale mel spectrogram loss) +- [Amphion](https://github.com/open-mmlab/Amphion) (for multi-scale sub-band CQT discriminator) diff --git a/src/third_party/BigVGAN/activations.py b/src/third_party/BigVGAN/activations.py new file mode 100644 index 0000000000000000000000000000000000000000..b1e83fcdef46300f5d5bff1f0dbf71f58f3b1186 --- /dev/null +++ b/src/third_party/BigVGAN/activations.py @@ -0,0 +1,126 @@ +# Implementation adapted from https://github.com/EdwardDixon/snake under the MIT license. +# LICENSE is in incl_licenses directory. + +import torch +from torch import nn, sin, pow +from torch.nn import Parameter + + +class Snake(nn.Module): + """ + Implementation of a sine-based periodic activation function + Shape: + - Input: (B, C, T) + - Output: (B, C, T), same shape as the input + Parameters: + - alpha - trainable parameter + References: + - This activation function is from this paper by Liu Ziyin, Tilman Hartwig, Masahito Ueda: + https://arxiv.org/abs/2006.08195 + Examples: + >>> a1 = snake(256) + >>> x = torch.randn(256) + >>> x = a1(x) + """ + + def __init__( + self, in_features, alpha=1.0, alpha_trainable=True, alpha_logscale=False + ): + """ + Initialization. + INPUT: + - in_features: shape of the input + - alpha: trainable parameter + alpha is initialized to 1 by default, higher values = higher-frequency. + alpha will be trained along with the rest of your model. + """ + super(Snake, self).__init__() + self.in_features = in_features + + # Initialize alpha + self.alpha_logscale = alpha_logscale + if self.alpha_logscale: # Log scale alphas initialized to zeros + self.alpha = Parameter(torch.zeros(in_features) * alpha) + else: # Linear scale alphas initialized to ones + self.alpha = Parameter(torch.ones(in_features) * alpha) + + self.alpha.requires_grad = alpha_trainable + + self.no_div_by_zero = 0.000000001 + + def forward(self, x): + """ + Forward pass of the function. + Applies the function to the input elementwise. + Snake ∶= x + 1/a * sin^2 (xa) + """ + alpha = self.alpha.unsqueeze(0).unsqueeze(-1) # Line up with x to [B, C, T] + if self.alpha_logscale: + alpha = torch.exp(alpha) + x = x + (1.0 / (alpha + self.no_div_by_zero)) * pow(sin(x * alpha), 2) + + return x + + +class SnakeBeta(nn.Module): + """ + A modified Snake function which uses separate parameters for the magnitude of the periodic components + Shape: + - Input: (B, C, T) + - Output: (B, C, T), same shape as the input + Parameters: + - alpha - trainable parameter that controls frequency + - beta - trainable parameter that controls magnitude + References: + - This activation function is a modified version based on this paper by Liu Ziyin, Tilman Hartwig, Masahito Ueda: + https://arxiv.org/abs/2006.08195 + Examples: + >>> a1 = snakebeta(256) + >>> x = torch.randn(256) + >>> x = a1(x) + """ + + def __init__( + self, in_features, alpha=1.0, alpha_trainable=True, alpha_logscale=False + ): + """ + Initialization. + INPUT: + - in_features: shape of the input + - alpha - trainable parameter that controls frequency + - beta - trainable parameter that controls magnitude + alpha is initialized to 1 by default, higher values = higher-frequency. + beta is initialized to 1 by default, higher values = higher-magnitude. + alpha will be trained along with the rest of your model. + """ + super(SnakeBeta, self).__init__() + self.in_features = in_features + + # Initialize alpha + self.alpha_logscale = alpha_logscale + if self.alpha_logscale: # Log scale alphas initialized to zeros + self.alpha = Parameter(torch.zeros(in_features) * alpha) + self.beta = Parameter(torch.zeros(in_features) * alpha) + else: # Linear scale alphas initialized to ones + self.alpha = Parameter(torch.ones(in_features) * alpha) + self.beta = Parameter(torch.ones(in_features) * alpha) + + self.alpha.requires_grad = alpha_trainable + self.beta.requires_grad = alpha_trainable + + self.no_div_by_zero = 0.000000001 + + def forward(self, x): + """ + Forward pass of the function. + Applies the function to the input elementwise. + SnakeBeta ∶= x + 1/b * sin^2 (xa) + """ + alpha = self.alpha.unsqueeze(0).unsqueeze(-1) # Line up with x to [B, C, T] + beta = self.beta.unsqueeze(0).unsqueeze(-1) + if self.alpha_logscale: + alpha = torch.exp(alpha) + beta = torch.exp(beta) + x = x + (1.0 / (beta + self.no_div_by_zero)) * pow(sin(x * alpha), 2) + + return x diff --git a/src/third_party/BigVGAN/alias_free_activation/cuda/__init__.py b/src/third_party/BigVGAN/alias_free_activation/cuda/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/src/third_party/BigVGAN/alias_free_activation/cuda/activation1d.py b/src/third_party/BigVGAN/alias_free_activation/cuda/activation1d.py new file mode 100644 index 0000000000000000000000000000000000000000..fc0d313cb265170943fb7cb16742b031038f7859 --- /dev/null +++ b/src/third_party/BigVGAN/alias_free_activation/cuda/activation1d.py @@ -0,0 +1,77 @@ +# Copyright (c) 2024 NVIDIA CORPORATION. +# Licensed under the MIT license. + +import torch +import torch.nn as nn +from alias_free_activation.torch.resample import UpSample1d, DownSample1d + +# load fused CUDA kernel: this enables importing anti_alias_activation_cuda +from alias_free_activation.cuda import load + +anti_alias_activation_cuda = load.load() + + +class FusedAntiAliasActivation(torch.autograd.Function): + """ + Assumes filter size 12, replication padding on upsampling/downsampling, and logscale alpha/beta parameters as inputs. + The hyperparameters are hard-coded in the kernel to maximize speed. + NOTE: The fused kenrel is incorrect for Activation1d with different hyperparameters. + """ + + @staticmethod + def forward(ctx, inputs, up_ftr, down_ftr, alpha, beta): + activation_results = anti_alias_activation_cuda.forward( + inputs, up_ftr, down_ftr, alpha, beta + ) + + return activation_results + + @staticmethod + def backward(ctx, output_grads): + raise NotImplementedError + return output_grads, None, None + + +class Activation1d(nn.Module): + def __init__( + self, + activation, + up_ratio: int = 2, + down_ratio: int = 2, + up_kernel_size: int = 12, + down_kernel_size: int = 12, + fused: bool = True, + ): + super().__init__() + self.up_ratio = up_ratio + self.down_ratio = down_ratio + self.act = activation + self.upsample = UpSample1d(up_ratio, up_kernel_size) + self.downsample = DownSample1d(down_ratio, down_kernel_size) + + self.fused = fused # Whether to use fused CUDA kernel or not + + def forward(self, x): + if not self.fused: + x = self.upsample(x) + x = self.act(x) + x = self.downsample(x) + return x + else: + if self.act.__class__.__name__ == "Snake": + beta = self.act.alpha.data # Snake uses same params for alpha and beta + else: + beta = ( + self.act.beta.data + ) # Snakebeta uses different params for alpha and beta + alpha = self.act.alpha.data + if ( + not self.act.alpha_logscale + ): # Exp baked into cuda kernel, cancel it out with a log + alpha = torch.log(alpha) + beta = torch.log(beta) + + x = FusedAntiAliasActivation.apply( + x, self.upsample.filter, self.downsample.lowpass.filter, alpha, beta + ) + return x diff --git a/src/third_party/BigVGAN/alias_free_activation/cuda/anti_alias_activation.cpp b/src/third_party/BigVGAN/alias_free_activation/cuda/anti_alias_activation.cpp new file mode 100644 index 0000000000000000000000000000000000000000..94fd90da386e66ce12a64ef243e4d125926dfd2a --- /dev/null +++ b/src/third_party/BigVGAN/alias_free_activation/cuda/anti_alias_activation.cpp @@ -0,0 +1,23 @@ +/* coding=utf-8 + * Copyright (c) 2024, NVIDIA CORPORATION. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + #include + +extern "C" torch::Tensor fwd_cuda(torch::Tensor const &input, torch::Tensor const &up_filter, torch::Tensor const &down_filter, torch::Tensor const &alpha, torch::Tensor const &beta); + +PYBIND11_MODULE(TORCH_EXTENSION_NAME, m) { + m.def("forward", &fwd_cuda, "Anti-Alias Activation forward (CUDA)"); +} \ No newline at end of file diff --git a/src/third_party/BigVGAN/alias_free_activation/cuda/anti_alias_activation_cuda.cu b/src/third_party/BigVGAN/alias_free_activation/cuda/anti_alias_activation_cuda.cu new file mode 100644 index 0000000000000000000000000000000000000000..7ee97492984a92c753f2357f03e7c04252060824 --- /dev/null +++ b/src/third_party/BigVGAN/alias_free_activation/cuda/anti_alias_activation_cuda.cu @@ -0,0 +1,246 @@ +/* coding=utf-8 + * Copyright (c) 2024, NVIDIA CORPORATION. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include +#include +#include +#include +#include "type_shim.h" +#include +#include +#include +#include +#include + +namespace +{ + // Hard-coded hyperparameters + // WARP_SIZE and WARP_BATCH must match the return values batches_per_warp and + constexpr int ELEMENTS_PER_LDG_STG = 1; //(WARP_ITERATIONS < 4) ? 1 : 4; + constexpr int BUFFER_SIZE = 32; + constexpr int FILTER_SIZE = 12; + constexpr int HALF_FILTER_SIZE = 6; + constexpr int UPSAMPLE_REPLICATION_PAD = 5; // 5 on each side, matching torch impl + constexpr int DOWNSAMPLE_REPLICATION_PAD_LEFT = 5; // matching torch impl + constexpr int DOWNSAMPLE_REPLICATION_PAD_RIGHT = 6; // matching torch impl + + template + __global__ void anti_alias_activation_forward( + output_t *dst, + const input_t *src, + const input_t *up_ftr, + const input_t *down_ftr, + const input_t *alpha, + const input_t *beta, + int batch_size, + int channels, + int seq_len) + { + // Up and downsample filters + input_t up_filter[FILTER_SIZE]; + input_t down_filter[FILTER_SIZE]; + + // Load data from global memory including extra indices reserved for replication paddings + input_t elements[2 * FILTER_SIZE + 2 * BUFFER_SIZE + 2 * UPSAMPLE_REPLICATION_PAD] = {0}; + input_t intermediates[2 * FILTER_SIZE + 2 * BUFFER_SIZE + DOWNSAMPLE_REPLICATION_PAD_LEFT + DOWNSAMPLE_REPLICATION_PAD_RIGHT] = {0}; + + // Output stores downsampled output before writing to dst + output_t output[BUFFER_SIZE]; + + // blockDim/threadIdx = (128, 1, 1) + // gridDim/blockIdx = (seq_blocks, channels, batches) + int block_offset = (blockIdx.x * 128 * BUFFER_SIZE + seq_len * (blockIdx.y + gridDim.y * blockIdx.z)); + int local_offset = threadIdx.x * BUFFER_SIZE; + int seq_offset = blockIdx.x * 128 * BUFFER_SIZE + local_offset; + + // intermediate have double the seq_len + int intermediate_local_offset = threadIdx.x * BUFFER_SIZE * 2; + int intermediate_seq_offset = blockIdx.x * 128 * BUFFER_SIZE * 2 + intermediate_local_offset; + + // Get values needed for replication padding before moving pointer + const input_t *right_most_pntr = src + (seq_len * (blockIdx.y + gridDim.y * blockIdx.z)); + input_t seq_left_most_value = right_most_pntr[0]; + input_t seq_right_most_value = right_most_pntr[seq_len - 1]; + + // Move src and dst pointers + src += block_offset + local_offset; + dst += block_offset + local_offset; + + // Alpha and beta values for snake activatons. Applies exp by default + alpha = alpha + blockIdx.y; + input_t alpha_val = expf(alpha[0]); + beta = beta + blockIdx.y; + input_t beta_val = expf(beta[0]); + + #pragma unroll + for (int it = 0; it < FILTER_SIZE; it += 1) + { + up_filter[it] = up_ftr[it]; + down_filter[it] = down_ftr[it]; + } + + // Apply replication padding for upsampling, matching torch impl + #pragma unroll + for (int it = -HALF_FILTER_SIZE; it < BUFFER_SIZE + HALF_FILTER_SIZE; it += 1) + { + int element_index = seq_offset + it; // index for element + if ((element_index < 0) && (element_index >= -UPSAMPLE_REPLICATION_PAD)) + { + elements[2 * (HALF_FILTER_SIZE + it)] = 2 * seq_left_most_value; + } + if ((element_index >= seq_len) && (element_index < seq_len + UPSAMPLE_REPLICATION_PAD)) + { + elements[2 * (HALF_FILTER_SIZE + it)] = 2 * seq_right_most_value; + } + if ((element_index >= 0) && (element_index < seq_len)) + { + elements[2 * (HALF_FILTER_SIZE + it)] = 2 * src[it]; + } + } + + // Apply upsampling strided convolution and write to intermediates. It reserves DOWNSAMPLE_REPLICATION_PAD_LEFT for replication padding of the downsampilng conv later + #pragma unroll + for (int it = 0; it < (2 * BUFFER_SIZE + 2 * FILTER_SIZE); it += 1) + { + input_t acc = 0.0; + int element_index = intermediate_seq_offset + it; // index for intermediate + #pragma unroll + for (int f_idx = 0; f_idx < FILTER_SIZE; f_idx += 1) + { + if ((element_index + f_idx) >= 0) + { + acc += up_filter[f_idx] * elements[it + f_idx]; + } + } + intermediates[it + DOWNSAMPLE_REPLICATION_PAD_LEFT] = acc; + } + + // Apply activation function. It reserves DOWNSAMPLE_REPLICATION_PAD_LEFT and DOWNSAMPLE_REPLICATION_PAD_RIGHT for replication padding of the downsampilng conv later + double no_div_by_zero = 0.000000001; + #pragma unroll + for (int it = 0; it < 2 * BUFFER_SIZE + 2 * FILTER_SIZE; it += 1) + { + intermediates[it + DOWNSAMPLE_REPLICATION_PAD_LEFT] += (1.0 / (beta_val + no_div_by_zero)) * sinf(intermediates[it + DOWNSAMPLE_REPLICATION_PAD_LEFT] * alpha_val) * sinf(intermediates[it + DOWNSAMPLE_REPLICATION_PAD_LEFT] * alpha_val); + } + + // Apply replication padding before downsampling conv from intermediates + #pragma unroll + for (int it = 0; it < DOWNSAMPLE_REPLICATION_PAD_LEFT; it += 1) + { + intermediates[it] = intermediates[DOWNSAMPLE_REPLICATION_PAD_LEFT]; + } + #pragma unroll + for (int it = DOWNSAMPLE_REPLICATION_PAD_LEFT + 2 * BUFFER_SIZE + 2 * FILTER_SIZE; it < DOWNSAMPLE_REPLICATION_PAD_LEFT + 2 * BUFFER_SIZE + 2 * FILTER_SIZE + DOWNSAMPLE_REPLICATION_PAD_RIGHT; it += 1) + { + intermediates[it] = intermediates[DOWNSAMPLE_REPLICATION_PAD_LEFT + 2 * BUFFER_SIZE + 2 * FILTER_SIZE - 1]; + } + + // Apply downsample strided convolution (assuming stride=2) from intermediates + #pragma unroll + for (int it = 0; it < BUFFER_SIZE; it += 1) + { + input_t acc = 0.0; + #pragma unroll + for (int f_idx = 0; f_idx < FILTER_SIZE; f_idx += 1) + { + // Add constant DOWNSAMPLE_REPLICATION_PAD_RIGHT to match torch implementation + acc += down_filter[f_idx] * intermediates[it * 2 + f_idx + DOWNSAMPLE_REPLICATION_PAD_RIGHT]; + } + output[it] = acc; + } + + // Write output to dst + #pragma unroll + for (int it = 0; it < BUFFER_SIZE; it += ELEMENTS_PER_LDG_STG) + { + int element_index = seq_offset + it; + if (element_index < seq_len) + { + dst[it] = output[it]; + } + } + + } + + template + void dispatch_anti_alias_activation_forward( + output_t *dst, + const input_t *src, + const input_t *up_ftr, + const input_t *down_ftr, + const input_t *alpha, + const input_t *beta, + int batch_size, + int channels, + int seq_len) + { + if (seq_len == 0) + { + return; + } + else + { + // Use 128 threads per block to maximimize gpu utilization + constexpr int threads_per_block = 128; + constexpr int seq_len_per_block = 4096; + int blocks_per_seq_len = (seq_len + seq_len_per_block - 1) / seq_len_per_block; + dim3 blocks(blocks_per_seq_len, channels, batch_size); + dim3 threads(threads_per_block, 1, 1); + + anti_alias_activation_forward + <<>>(dst, src, up_ftr, down_ftr, alpha, beta, batch_size, channels, seq_len); + } + } +} + +extern "C" torch::Tensor fwd_cuda(torch::Tensor const &input, torch::Tensor const &up_filter, torch::Tensor const &down_filter, torch::Tensor const &alpha, torch::Tensor const &beta) +{ + // Input is a 3d tensor with dimensions [batches, channels, seq_len] + const int batches = input.size(0); + const int channels = input.size(1); + const int seq_len = input.size(2); + + // Output + auto act_options = input.options().requires_grad(false); + + torch::Tensor anti_alias_activation_results = + torch::empty({batches, channels, seq_len}, act_options); + + void *input_ptr = static_cast(input.data_ptr()); + void *up_filter_ptr = static_cast(up_filter.data_ptr()); + void *down_filter_ptr = static_cast(down_filter.data_ptr()); + void *alpha_ptr = static_cast(alpha.data_ptr()); + void *beta_ptr = static_cast(beta.data_ptr()); + void *anti_alias_activation_results_ptr = static_cast(anti_alias_activation_results.data_ptr()); + + DISPATCH_FLOAT_HALF_AND_BFLOAT( + input.scalar_type(), + "dispatch anti alias activation_forward", + dispatch_anti_alias_activation_forward( + reinterpret_cast(anti_alias_activation_results_ptr), + reinterpret_cast(input_ptr), + reinterpret_cast(up_filter_ptr), + reinterpret_cast(down_filter_ptr), + reinterpret_cast(alpha_ptr), + reinterpret_cast(beta_ptr), + batches, + channels, + seq_len);); + return anti_alias_activation_results; +} \ No newline at end of file diff --git a/src/third_party/BigVGAN/alias_free_activation/cuda/compat.h b/src/third_party/BigVGAN/alias_free_activation/cuda/compat.h new file mode 100644 index 0000000000000000000000000000000000000000..0f93af5700470e7f6066af7dbe56aced98ea32d9 --- /dev/null +++ b/src/third_party/BigVGAN/alias_free_activation/cuda/compat.h @@ -0,0 +1,29 @@ +/* coding=utf-8 + * Copyright (c) 2020, NVIDIA CORPORATION. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/*This code is copied fron NVIDIA apex: + * https://github.com/NVIDIA/apex + * with minor changes. */ + +#ifndef TORCH_CHECK +#define TORCH_CHECK AT_CHECK +#endif + +#ifdef VERSION_GE_1_3 +#define DATA_PTR data_ptr +#else +#define DATA_PTR data +#endif diff --git a/src/third_party/BigVGAN/alias_free_activation/cuda/load.py b/src/third_party/BigVGAN/alias_free_activation/cuda/load.py new file mode 100644 index 0000000000000000000000000000000000000000..82afde3d73dda72b06af28a622fdab1954825a28 --- /dev/null +++ b/src/third_party/BigVGAN/alias_free_activation/cuda/load.py @@ -0,0 +1,86 @@ +# Copyright (c) 2024 NVIDIA CORPORATION. +# Licensed under the MIT license. + +import os +import pathlib +import subprocess + +from torch.utils import cpp_extension + +""" +Setting this param to a list has a problem of generating different compilation commands (with diferent order of architectures) and leading to recompilation of fused kernels. +Set it to empty stringo avoid recompilation and assign arch flags explicity in extra_cuda_cflags below +""" +os.environ["TORCH_CUDA_ARCH_LIST"] = "" + + +def load(): + # Check if cuda 11 is installed for compute capability 8.0 + cc_flag = [] + _, bare_metal_major, _ = _get_cuda_bare_metal_version(cpp_extension.CUDA_HOME) + if int(bare_metal_major) >= 11: + cc_flag.append("-gencode") + cc_flag.append("arch=compute_80,code=sm_80") + + # Build path + srcpath = pathlib.Path(__file__).parent.absolute() + buildpath = srcpath / "build" + _create_build_dir(buildpath) + + # Helper function to build the kernels. + def _cpp_extention_load_helper(name, sources, extra_cuda_flags): + return cpp_extension.load( + name=name, + sources=sources, + build_directory=buildpath, + extra_cflags=[ + "-O3", + ], + extra_cuda_cflags=[ + "-O3", + "-gencode", + "arch=compute_70,code=sm_70", + "--use_fast_math", + ] + + extra_cuda_flags + + cc_flag, + verbose=True, + ) + + extra_cuda_flags = [ + "-U__CUDA_NO_HALF_OPERATORS__", + "-U__CUDA_NO_HALF_CONVERSIONS__", + "--expt-relaxed-constexpr", + "--expt-extended-lambda", + ] + + sources = [ + srcpath / "anti_alias_activation.cpp", + srcpath / "anti_alias_activation_cuda.cu", + ] + anti_alias_activation_cuda = _cpp_extention_load_helper( + "anti_alias_activation_cuda", sources, extra_cuda_flags + ) + + return anti_alias_activation_cuda + + +def _get_cuda_bare_metal_version(cuda_dir): + raw_output = subprocess.check_output( + [cuda_dir + "/bin/nvcc", "-V"], universal_newlines=True + ) + output = raw_output.split() + release_idx = output.index("release") + 1 + release = output[release_idx].split(".") + bare_metal_major = release[0] + bare_metal_minor = release[1][0] + + return raw_output, bare_metal_major, bare_metal_minor + + +def _create_build_dir(buildpath): + try: + os.mkdir(buildpath) + except OSError: + if not os.path.isdir(buildpath): + print(f"Creation of the build directory {buildpath} failed") diff --git a/src/third_party/BigVGAN/alias_free_activation/cuda/type_shim.h b/src/third_party/BigVGAN/alias_free_activation/cuda/type_shim.h new file mode 100644 index 0000000000000000000000000000000000000000..4328d0369a5fb8730cdf236d9f267453f4201d84 --- /dev/null +++ b/src/third_party/BigVGAN/alias_free_activation/cuda/type_shim.h @@ -0,0 +1,92 @@ +/* coding=utf-8 + * Copyright (c) 2020, NVIDIA CORPORATION. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include "compat.h" + +#define DISPATCH_FLOAT_HALF_AND_BFLOAT(TYPE, NAME, ...) \ + switch (TYPE) \ + { \ + case at::ScalarType::Float: \ + { \ + using scalar_t = float; \ + __VA_ARGS__; \ + break; \ + } \ + case at::ScalarType::Half: \ + { \ + using scalar_t = at::Half; \ + __VA_ARGS__; \ + break; \ + } \ + case at::ScalarType::BFloat16: \ + { \ + using scalar_t = at::BFloat16; \ + __VA_ARGS__; \ + break; \ + } \ + default: \ + AT_ERROR(#NAME, " not implemented for '", toString(TYPE), "'"); \ + } + +#define DISPATCH_FLOAT_HALF_AND_BFLOAT_INOUT_TYPES(TYPEIN, TYPEOUT, NAME, ...) \ + switch (TYPEIN) \ + { \ + case at::ScalarType::Float: \ + { \ + using scalar_t_in = float; \ + switch (TYPEOUT) \ + { \ + case at::ScalarType::Float: \ + { \ + using scalar_t_out = float; \ + __VA_ARGS__; \ + break; \ + } \ + case at::ScalarType::Half: \ + { \ + using scalar_t_out = at::Half; \ + __VA_ARGS__; \ + break; \ + } \ + case at::ScalarType::BFloat16: \ + { \ + using scalar_t_out = at::BFloat16; \ + __VA_ARGS__; \ + break; \ + } \ + default: \ + AT_ERROR(#NAME, " not implemented for '", toString(TYPEOUT), "'"); \ + } \ + break; \ + } \ + case at::ScalarType::Half: \ + { \ + using scalar_t_in = at::Half; \ + using scalar_t_out = at::Half; \ + __VA_ARGS__; \ + break; \ + } \ + case at::ScalarType::BFloat16: \ + { \ + using scalar_t_in = at::BFloat16; \ + using scalar_t_out = at::BFloat16; \ + __VA_ARGS__; \ + break; \ + } \ + default: \ + AT_ERROR(#NAME, " not implemented for '", toString(TYPEIN), "'"); \ + } diff --git a/src/third_party/BigVGAN/alias_free_activation/torch/__init__.py b/src/third_party/BigVGAN/alias_free_activation/torch/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..7bb0ad84ef184dcb15464c8ca827ae1c284f8990 --- /dev/null +++ b/src/third_party/BigVGAN/alias_free_activation/torch/__init__.py @@ -0,0 +1,6 @@ +# Adapted from https://github.com/junjun3518/alias-free-torch under the Apache License 2.0 +# LICENSE is in incl_licenses directory. + +from .filter import * +from .resample import * +from .act import * diff --git a/src/third_party/BigVGAN/alias_free_activation/torch/act.py b/src/third_party/BigVGAN/alias_free_activation/torch/act.py new file mode 100644 index 0000000000000000000000000000000000000000..421a1cdd470e462b263c50db4d82bad2d0fe552e --- /dev/null +++ b/src/third_party/BigVGAN/alias_free_activation/torch/act.py @@ -0,0 +1,30 @@ +# Adapted from https://github.com/junjun3518/alias-free-torch under the Apache License 2.0 +# LICENSE is in incl_licenses directory. + +import torch.nn as nn +from alias_free_activation.torch.resample import UpSample1d, DownSample1d + + +class Activation1d(nn.Module): + def __init__( + self, + activation, + up_ratio: int = 2, + down_ratio: int = 2, + up_kernel_size: int = 12, + down_kernel_size: int = 12, + ): + super().__init__() + self.up_ratio = up_ratio + self.down_ratio = down_ratio + self.act = activation + self.upsample = UpSample1d(up_ratio, up_kernel_size) + self.downsample = DownSample1d(down_ratio, down_kernel_size) + + # x: [B,C,T] + def forward(self, x): + x = self.upsample(x) + x = self.act(x) + x = self.downsample(x) + + return x diff --git a/src/third_party/BigVGAN/alias_free_activation/torch/filter.py b/src/third_party/BigVGAN/alias_free_activation/torch/filter.py new file mode 100644 index 0000000000000000000000000000000000000000..81a4a9a7cefb457f8880f54385335180fbd43f1b --- /dev/null +++ b/src/third_party/BigVGAN/alias_free_activation/torch/filter.py @@ -0,0 +1,101 @@ +# Adapted from https://github.com/junjun3518/alias-free-torch under the Apache License 2.0 +# LICENSE is in incl_licenses directory. + +import torch +import torch.nn as nn +import torch.nn.functional as F +import math + +if "sinc" in dir(torch): + sinc = torch.sinc +else: + # This code is adopted from adefossez's julius.core.sinc under the MIT License + # https://adefossez.github.io/julius/julius/core.html + # LICENSE is in incl_licenses directory. + def sinc(x: torch.Tensor): + """ + Implementation of sinc, i.e. sin(pi * x) / (pi * x) + __Warning__: Different to julius.sinc, the input is multiplied by `pi`! + """ + return torch.where( + x == 0, + torch.tensor(1.0, device=x.device, dtype=x.dtype), + torch.sin(math.pi * x) / math.pi / x, + ) + + +# This code is adopted from adefossez's julius.lowpass.LowPassFilters under the MIT License +# https://adefossez.github.io/julius/julius/lowpass.html +# LICENSE is in incl_licenses directory. +def kaiser_sinc_filter1d( + cutoff, half_width, kernel_size +): # return filter [1,1,kernel_size] + even = kernel_size % 2 == 0 + half_size = kernel_size // 2 + + # For kaiser window + delta_f = 4 * half_width + A = 2.285 * (half_size - 1) * math.pi * delta_f + 7.95 + if A > 50.0: + beta = 0.1102 * (A - 8.7) + elif A >= 21.0: + beta = 0.5842 * (A - 21) ** 0.4 + 0.07886 * (A - 21.0) + else: + beta = 0.0 + window = torch.kaiser_window(kernel_size, beta=beta, periodic=False) + + # ratio = 0.5/cutoff -> 2 * cutoff = 1 / ratio + if even: + time = torch.arange(-half_size, half_size) + 0.5 + else: + time = torch.arange(kernel_size) - half_size + if cutoff == 0: + filter_ = torch.zeros_like(time) + else: + filter_ = 2 * cutoff * window * sinc(2 * cutoff * time) + """ + Normalize filter to have sum = 1, otherwise we will have a small leakage of the constant component in the input signal. + """ + filter_ /= filter_.sum() + filter = filter_.view(1, 1, kernel_size) + + return filter + + +class LowPassFilter1d(nn.Module): + def __init__( + self, + cutoff=0.5, + half_width=0.6, + stride: int = 1, + padding: bool = True, + padding_mode: str = "replicate", + kernel_size: int = 12, + ): + """ + kernel_size should be even number for stylegan3 setup, in this implementation, odd number is also possible. + """ + super().__init__() + if cutoff < -0.0: + raise ValueError("Minimum cutoff must be larger than zero.") + if cutoff > 0.5: + raise ValueError("A cutoff above 0.5 does not make sense.") + self.kernel_size = kernel_size + self.even = kernel_size % 2 == 0 + self.pad_left = kernel_size // 2 - int(self.even) + self.pad_right = kernel_size // 2 + self.stride = stride + self.padding = padding + self.padding_mode = padding_mode + filter = kaiser_sinc_filter1d(cutoff, half_width, kernel_size) + self.register_buffer("filter", filter) + + # Input [B, C, T] + def forward(self, x): + _, C, _ = x.shape + + if self.padding: + x = F.pad(x, (self.pad_left, self.pad_right), mode=self.padding_mode) + out = F.conv1d(x, self.filter.expand(C, -1, -1), stride=self.stride, groups=C) + + return out diff --git a/src/third_party/BigVGAN/alias_free_activation/torch/resample.py b/src/third_party/BigVGAN/alias_free_activation/torch/resample.py new file mode 100644 index 0000000000000000000000000000000000000000..c2cf40e9a24da7da3b707659cec274860102a11f --- /dev/null +++ b/src/third_party/BigVGAN/alias_free_activation/torch/resample.py @@ -0,0 +1,58 @@ +# Adapted from https://github.com/junjun3518/alias-free-torch under the Apache License 2.0 +# LICENSE is in incl_licenses directory. + +import torch.nn as nn +from torch.nn import functional as F +from alias_free_activation.torch.filter import LowPassFilter1d +from alias_free_activation.torch.filter import kaiser_sinc_filter1d + + +class UpSample1d(nn.Module): + def __init__(self, ratio=2, kernel_size=None): + super().__init__() + self.ratio = ratio + self.kernel_size = ( + int(6 * ratio // 2) * 2 if kernel_size is None else kernel_size + ) + self.stride = ratio + self.pad = self.kernel_size // ratio - 1 + self.pad_left = self.pad * self.stride + (self.kernel_size - self.stride) // 2 + self.pad_right = ( + self.pad * self.stride + (self.kernel_size - self.stride + 1) // 2 + ) + filter = kaiser_sinc_filter1d( + cutoff=0.5 / ratio, half_width=0.6 / ratio, kernel_size=self.kernel_size + ) + self.register_buffer("filter", filter) + + # x: [B, C, T] + def forward(self, x): + _, C, _ = x.shape + + x = F.pad(x, (self.pad, self.pad), mode="replicate") + x = self.ratio * F.conv_transpose1d( + x, self.filter.expand(C, -1, -1), stride=self.stride, groups=C + ) + x = x[..., self.pad_left : -self.pad_right] + + return x + + +class DownSample1d(nn.Module): + def __init__(self, ratio=2, kernel_size=None): + super().__init__() + self.ratio = ratio + self.kernel_size = ( + int(6 * ratio // 2) * 2 if kernel_size is None else kernel_size + ) + self.lowpass = LowPassFilter1d( + cutoff=0.5 / ratio, + half_width=0.6 / ratio, + stride=ratio, + kernel_size=self.kernel_size, + ) + + def forward(self, x): + xx = self.lowpass(x) + + return xx diff --git a/src/third_party/BigVGAN/bigvgan.py b/src/third_party/BigVGAN/bigvgan.py new file mode 100644 index 0000000000000000000000000000000000000000..40407c3ca142bf7e069aba066486cfe1b3ab9016 --- /dev/null +++ b/src/third_party/BigVGAN/bigvgan.py @@ -0,0 +1,493 @@ +# Copyright (c) 2024 NVIDIA CORPORATION. +# Licensed under the MIT license. + +# Adapted from https://github.com/jik876/hifi-gan under the MIT license. +# LICENSE is in incl_licenses directory. + +import os +import json +from pathlib import Path +from typing import Optional, Union, Dict + +import torch +import torch.nn as nn +from torch.nn import Conv1d, ConvTranspose1d +from torch.nn.utils import weight_norm, remove_weight_norm + +import activations +from utils import init_weights, get_padding +from alias_free_activation.torch.act import Activation1d as TorchActivation1d +from env import AttrDict + +from huggingface_hub import PyTorchModelHubMixin, hf_hub_download + + +def load_hparams_from_json(path) -> AttrDict: + with open(path) as f: + data = f.read() + return AttrDict(json.loads(data)) + + +class AMPBlock1(torch.nn.Module): + """ + AMPBlock applies Snake / SnakeBeta activation functions with trainable parameters that control periodicity, defined for each layer. + AMPBlock1 has additional self.convs2 that contains additional Conv1d layers with a fixed dilation=1 followed by each layer in self.convs1 + + Args: + h (AttrDict): Hyperparameters. + channels (int): Number of convolution channels. + kernel_size (int): Size of the convolution kernel. Default is 3. + dilation (tuple): Dilation rates for the convolutions. Each dilation layer has two convolutions. Default is (1, 3, 5). + activation (str): Activation function type. Should be either 'snake' or 'snakebeta'. Default is None. + """ + + def __init__( + self, + h: AttrDict, + channels: int, + kernel_size: int = 3, + dilation: tuple = (1, 3, 5), + activation: str = None, + ): + super().__init__() + + self.h = h + + self.convs1 = nn.ModuleList( + [ + weight_norm( + Conv1d( + channels, + channels, + kernel_size, + stride=1, + dilation=d, + padding=get_padding(kernel_size, d), + ) + ) + for d in dilation + ] + ) + self.convs1.apply(init_weights) + + self.convs2 = nn.ModuleList( + [ + weight_norm( + Conv1d( + channels, + channels, + kernel_size, + stride=1, + dilation=1, + padding=get_padding(kernel_size, 1), + ) + ) + for _ in range(len(dilation)) + ] + ) + self.convs2.apply(init_weights) + + self.num_layers = len(self.convs1) + len( + self.convs2 + ) # Total number of conv layers + + # Select which Activation1d, lazy-load cuda version to ensure backward compatibility + if self.h.get("use_cuda_kernel", False): + from alias_free_activation.cuda.activation1d import ( + Activation1d as CudaActivation1d, + ) + + Activation1d = CudaActivation1d + else: + Activation1d = TorchActivation1d + + # Activation functions + if activation == "snake": + self.activations = nn.ModuleList( + [ + Activation1d( + activation=activations.Snake( + channels, alpha_logscale=h.snake_logscale + ) + ) + for _ in range(self.num_layers) + ] + ) + elif activation == "snakebeta": + self.activations = nn.ModuleList( + [ + Activation1d( + activation=activations.SnakeBeta( + channels, alpha_logscale=h.snake_logscale + ) + ) + for _ in range(self.num_layers) + ] + ) + else: + raise NotImplementedError( + "activation incorrectly specified. check the config file and look for 'activation'." + ) + + def forward(self, x): + acts1, acts2 = self.activations[::2], self.activations[1::2] + for c1, c2, a1, a2 in zip(self.convs1, self.convs2, acts1, acts2): + xt = a1(x) + xt = c1(xt) + xt = a2(xt) + xt = c2(xt) + x = xt + x + + return x + + def remove_weight_norm(self): + for l in self.convs1: + remove_weight_norm(l) + for l in self.convs2: + remove_weight_norm(l) + + +class AMPBlock2(torch.nn.Module): + """ + AMPBlock applies Snake / SnakeBeta activation functions with trainable parameters that control periodicity, defined for each layer. + Unlike AMPBlock1, AMPBlock2 does not contain extra Conv1d layers with fixed dilation=1 + + Args: + h (AttrDict): Hyperparameters. + channels (int): Number of convolution channels. + kernel_size (int): Size of the convolution kernel. Default is 3. + dilation (tuple): Dilation rates for the convolutions. Each dilation layer has two convolutions. Default is (1, 3, 5). + activation (str): Activation function type. Should be either 'snake' or 'snakebeta'. Default is None. + """ + + def __init__( + self, + h: AttrDict, + channels: int, + kernel_size: int = 3, + dilation: tuple = (1, 3, 5), + activation: str = None, + ): + super().__init__() + + self.h = h + + self.convs = nn.ModuleList( + [ + weight_norm( + Conv1d( + channels, + channels, + kernel_size, + stride=1, + dilation=d, + padding=get_padding(kernel_size, d), + ) + ) + for d in dilation + ] + ) + self.convs.apply(init_weights) + + self.num_layers = len(self.convs) # Total number of conv layers + + # Select which Activation1d, lazy-load cuda version to ensure backward compatibility + if self.h.get("use_cuda_kernel", False): + from alias_free_activation.cuda.activation1d import ( + Activation1d as CudaActivation1d, + ) + + Activation1d = CudaActivation1d + else: + Activation1d = TorchActivation1d + + # Activation functions + if activation == "snake": + self.activations = nn.ModuleList( + [ + Activation1d( + activation=activations.Snake( + channels, alpha_logscale=h.snake_logscale + ) + ) + for _ in range(self.num_layers) + ] + ) + elif activation == "snakebeta": + self.activations = nn.ModuleList( + [ + Activation1d( + activation=activations.SnakeBeta( + channels, alpha_logscale=h.snake_logscale + ) + ) + for _ in range(self.num_layers) + ] + ) + else: + raise NotImplementedError( + "activation incorrectly specified. check the config file and look for 'activation'." + ) + + def forward(self, x): + for c, a in zip(self.convs, self.activations): + xt = a(x) + xt = c(xt) + x = xt + x + return x + + def remove_weight_norm(self): + for l in self.convs: + remove_weight_norm(l) + + +class BigVGAN( + torch.nn.Module, + PyTorchModelHubMixin, + library_name="bigvgan", + repo_url="https://github.com/NVIDIA/BigVGAN", + docs_url="https://github.com/NVIDIA/BigVGAN/blob/main/README.md", + pipeline_tag="audio-to-audio", + license="mit", + tags=["neural-vocoder", "audio-generation", "arxiv:2206.04658"], +): + """ + BigVGAN is a neural vocoder model that applies anti-aliased periodic activation for residual blocks (resblocks). + New in BigVGAN-v2: it can optionally use optimized CUDA kernels for AMP (anti-aliased multi-periodicity) blocks. + + Args: + h (AttrDict): Hyperparameters. + use_cuda_kernel (bool): If set to True, loads optimized CUDA kernels for AMP. This should be used for inference only, as training is not supported with CUDA kernels. + + Note: + - The `use_cuda_kernel` parameter should be used for inference only, as training with CUDA kernels is not supported. + - Ensure that the activation function is correctly specified in the hyperparameters (h.activation). + """ + + def __init__(self, h: AttrDict, use_cuda_kernel: bool = False): + super().__init__() + self.h = h + self.h["use_cuda_kernel"] = use_cuda_kernel + + # Select which Activation1d, lazy-load cuda version to ensure backward compatibility + if self.h.get("use_cuda_kernel", False): + from alias_free_activation.cuda.activation1d import ( + Activation1d as CudaActivation1d, + ) + + Activation1d = CudaActivation1d + else: + Activation1d = TorchActivation1d + + self.num_kernels = len(h.resblock_kernel_sizes) + self.num_upsamples = len(h.upsample_rates) + + # Pre-conv + self.conv_pre = weight_norm( + Conv1d(h.num_mels, h.upsample_initial_channel, 7, 1, padding=3) + ) + + # Define which AMPBlock to use. BigVGAN uses AMPBlock1 as default + if h.resblock == "1": + resblock_class = AMPBlock1 + elif h.resblock == "2": + resblock_class = AMPBlock2 + else: + raise ValueError( + f"Incorrect resblock class specified in hyperparameters. Got {h.resblock}" + ) + + # Transposed conv-based upsamplers. does not apply anti-aliasing + self.ups = nn.ModuleList() + for i, (u, k) in enumerate(zip(h.upsample_rates, h.upsample_kernel_sizes)): + self.ups.append( + nn.ModuleList( + [ + weight_norm( + ConvTranspose1d( + h.upsample_initial_channel // (2**i), + h.upsample_initial_channel // (2 ** (i + 1)), + k, + u, + padding=(k - u) // 2, + ) + ) + ] + ) + ) + + # Residual blocks using anti-aliased multi-periodicity composition modules (AMP) + self.resblocks = nn.ModuleList() + for i in range(len(self.ups)): + ch = h.upsample_initial_channel // (2 ** (i + 1)) + for j, (k, d) in enumerate( + zip(h.resblock_kernel_sizes, h.resblock_dilation_sizes) + ): + self.resblocks.append( + resblock_class(h, ch, k, d, activation=h.activation) + ) + + # Post-conv + activation_post = ( + activations.Snake(ch, alpha_logscale=h.snake_logscale) + if h.activation == "snake" + else ( + activations.SnakeBeta(ch, alpha_logscale=h.snake_logscale) + if h.activation == "snakebeta" + else None + ) + ) + if activation_post is None: + raise NotImplementedError( + "activation incorrectly specified. check the config file and look for 'activation'." + ) + + self.activation_post = Activation1d(activation=activation_post) + + # Whether to use bias for the final conv_post. Default to True for backward compatibility + self.use_bias_at_final = h.get("use_bias_at_final", True) + self.conv_post = weight_norm( + Conv1d(ch, 1, 7, 1, padding=3, bias=self.use_bias_at_final) + ) + + # Weight initialization + for i in range(len(self.ups)): + self.ups[i].apply(init_weights) + self.conv_post.apply(init_weights) + + # Final tanh activation. Defaults to True for backward compatibility + self.use_tanh_at_final = h.get("use_tanh_at_final", True) + + def forward(self, x): + # Pre-conv + x = self.conv_pre(x) + + for i in range(self.num_upsamples): + # Upsampling + for i_up in range(len(self.ups[i])): + x = self.ups[i][i_up](x) + # AMP blocks + xs = None + for j in range(self.num_kernels): + if xs is None: + xs = self.resblocks[i * self.num_kernels + j](x) + else: + xs += self.resblocks[i * self.num_kernels + j](x) + x = xs / self.num_kernels + + # Post-conv + x = self.activation_post(x) + x = self.conv_post(x) + # Final tanh activation + if self.use_tanh_at_final: + x = torch.tanh(x) + else: + x = torch.clamp(x, min=-1.0, max=1.0) # Bound the output to [-1, 1] + + return x + + def remove_weight_norm(self): + try: + print("Removing weight norm...") + for l in self.ups: + for l_i in l: + remove_weight_norm(l_i) + for l in self.resblocks: + l.remove_weight_norm() + remove_weight_norm(self.conv_pre) + remove_weight_norm(self.conv_post) + except ValueError: + print("[INFO] Model already removed weight norm. Skipping!") + pass + + # Additional methods for huggingface_hub support + def _save_pretrained(self, save_directory: Path) -> None: + """Save weights and config.json from a Pytorch model to a local directory.""" + + model_path = save_directory / "bigvgan_generator.pt" + torch.save({"generator": self.state_dict()}, model_path) + + config_path = save_directory / "config.json" + with open(config_path, "w") as config_file: + json.dump(self.h, config_file, indent=4) + + @classmethod + def _from_pretrained( + cls, + *, + model_id: str, + revision: str, + cache_dir: str, + force_download: bool, + proxies: Optional[Dict], + resume_download: bool, + local_files_only: bool, + token: Union[str, bool, None], + map_location: str = "cpu", # Additional argument + strict: bool = False, # Additional argument + use_cuda_kernel: bool = False, + **model_kwargs, + ): + """Load Pytorch pretrained weights and return the loaded model.""" + + # Download and load hyperparameters (h) used by BigVGAN + if os.path.isdir(model_id): + print("Loading config.json from local directory") + config_file = os.path.join(model_id, "config.json") + else: + config_file = hf_hub_download( + repo_id=model_id, + filename="config.json", + revision=revision, + cache_dir=cache_dir, + force_download=force_download, + proxies=proxies, + resume_download=resume_download, + token=token, + local_files_only=local_files_only, + ) + h = load_hparams_from_json(config_file) + + # instantiate BigVGAN using h + if use_cuda_kernel: + print( + f"[WARNING] You have specified use_cuda_kernel=True during BigVGAN.from_pretrained(). Only inference is supported (training is not implemented)!" + ) + print( + f"[WARNING] You need nvcc and ninja installed in your system that matches your PyTorch build is using to build the kernel. If not, the model will fail to initialize or generate incorrect waveform!" + ) + print( + f"[WARNING] For detail, see the official GitHub repository: https://github.com/NVIDIA/BigVGAN?tab=readme-ov-file#using-custom-cuda-kernel-for-synthesis" + ) + model = cls(h, use_cuda_kernel=use_cuda_kernel) + + # Download and load pretrained generator weight + if os.path.isdir(model_id): + print("Loading weights from local directory") + model_file = os.path.join(model_id, "bigvgan_generator.pt") + else: + print(f"Loading weights from {model_id}") + model_file = hf_hub_download( + repo_id=model_id, + filename="bigvgan_generator.pt", + revision=revision, + cache_dir=cache_dir, + force_download=force_download, + proxies=proxies, + resume_download=resume_download, + token=token, + local_files_only=local_files_only, + ) + + checkpoint_dict = torch.load(model_file, map_location=map_location) + + try: + model.load_state_dict(checkpoint_dict["generator"]) + except RuntimeError: + print( + f"[INFO] the pretrained checkpoint does not contain weight norm. Loading the checkpoint after removing weight norm!" + ) + model.remove_weight_norm() + model.load_state_dict(checkpoint_dict["generator"]) + + return model diff --git a/src/third_party/BigVGAN/configs/bigvgan_22khz_80band.json b/src/third_party/BigVGAN/configs/bigvgan_22khz_80band.json new file mode 100644 index 0000000000000000000000000000000000000000..00a0f1f95e10035436a6b8f0130c7289c83b8368 --- /dev/null +++ b/src/third_party/BigVGAN/configs/bigvgan_22khz_80band.json @@ -0,0 +1,45 @@ +{ + "resblock": "1", + "num_gpus": 0, + "batch_size": 32, + "learning_rate": 0.0001, + "adam_b1": 0.8, + "adam_b2": 0.99, + "lr_decay": 0.9999996, + "seed": 1234, + + "upsample_rates": [4,4,2,2,2,2], + "upsample_kernel_sizes": [8,8,4,4,4,4], + "upsample_initial_channel": 1536, + "resblock_kernel_sizes": [3,7,11], + "resblock_dilation_sizes": [[1,3,5], [1,3,5], [1,3,5]], + + "activation": "snakebeta", + "snake_logscale": true, + + "resolutions": [[1024, 120, 600], [2048, 240, 1200], [512, 50, 240]], + "mpd_reshapes": [2, 3, 5, 7, 11], + "use_spectral_norm": false, + "discriminator_channel_mult": 1, + + "segment_size": 8192, + "num_mels": 80, + "num_freq": 1025, + "n_fft": 1024, + "hop_size": 256, + "win_size": 1024, + + "sampling_rate": 22050, + + "fmin": 0, + "fmax": 8000, + "fmax_for_loss": null, + + "num_workers": 4, + + "dist_config": { + "dist_backend": "nccl", + "dist_url": "tcp://localhost:54321", + "world_size": 1 + } +} diff --git a/src/third_party/BigVGAN/configs/bigvgan_24khz_100band.json b/src/third_party/BigVGAN/configs/bigvgan_24khz_100band.json new file mode 100644 index 0000000000000000000000000000000000000000..6a2d63c5249236df1318a72b7940eef19b400d90 --- /dev/null +++ b/src/third_party/BigVGAN/configs/bigvgan_24khz_100band.json @@ -0,0 +1,45 @@ +{ + "resblock": "1", + "num_gpus": 0, + "batch_size": 32, + "learning_rate": 0.0001, + "adam_b1": 0.8, + "adam_b2": 0.99, + "lr_decay": 0.9999996, + "seed": 1234, + + "upsample_rates": [4,4,2,2,2,2], + "upsample_kernel_sizes": [8,8,4,4,4,4], + "upsample_initial_channel": 1536, + "resblock_kernel_sizes": [3,7,11], + "resblock_dilation_sizes": [[1,3,5], [1,3,5], [1,3,5]], + + "activation": "snakebeta", + "snake_logscale": true, + + "resolutions": [[1024, 120, 600], [2048, 240, 1200], [512, 50, 240]], + "mpd_reshapes": [2, 3, 5, 7, 11], + "use_spectral_norm": false, + "discriminator_channel_mult": 1, + + "segment_size": 8192, + "num_mels": 100, + "num_freq": 1025, + "n_fft": 1024, + "hop_size": 256, + "win_size": 1024, + + "sampling_rate": 24000, + + "fmin": 0, + "fmax": 12000, + "fmax_for_loss": null, + + "num_workers": 4, + + "dist_config": { + "dist_backend": "nccl", + "dist_url": "tcp://localhost:54321", + "world_size": 1 + } +} diff --git a/src/third_party/BigVGAN/configs/bigvgan_base_22khz_80band.json b/src/third_party/BigVGAN/configs/bigvgan_base_22khz_80band.json new file mode 100644 index 0000000000000000000000000000000000000000..df5c56a8dd57f600a03611a8c8044224ad1f43c1 --- /dev/null +++ b/src/third_party/BigVGAN/configs/bigvgan_base_22khz_80band.json @@ -0,0 +1,45 @@ +{ + "resblock": "1", + "num_gpus": 0, + "batch_size": 32, + "learning_rate": 0.0001, + "adam_b1": 0.8, + "adam_b2": 0.99, + "lr_decay": 0.9999996, + "seed": 1234, + + "upsample_rates": [8,8,2,2], + "upsample_kernel_sizes": [16,16,4,4], + "upsample_initial_channel": 512, + "resblock_kernel_sizes": [3,7,11], + "resblock_dilation_sizes": [[1,3,5], [1,3,5], [1,3,5]], + + "activation": "snakebeta", + "snake_logscale": true, + + "resolutions": [[1024, 120, 600], [2048, 240, 1200], [512, 50, 240]], + "mpd_reshapes": [2, 3, 5, 7, 11], + "use_spectral_norm": false, + "discriminator_channel_mult": 1, + + "segment_size": 8192, + "num_mels": 80, + "num_freq": 1025, + "n_fft": 1024, + "hop_size": 256, + "win_size": 1024, + + "sampling_rate": 22050, + + "fmin": 0, + "fmax": 8000, + "fmax_for_loss": null, + + "num_workers": 4, + + "dist_config": { + "dist_backend": "nccl", + "dist_url": "tcp://localhost:54321", + "world_size": 1 + } +} diff --git a/src/third_party/BigVGAN/configs/bigvgan_base_24khz_100band.json b/src/third_party/BigVGAN/configs/bigvgan_base_24khz_100band.json new file mode 100644 index 0000000000000000000000000000000000000000..8673a7ed0258629cdb49efc2aad189c6ecd64e1c --- /dev/null +++ b/src/third_party/BigVGAN/configs/bigvgan_base_24khz_100band.json @@ -0,0 +1,45 @@ +{ + "resblock": "1", + "num_gpus": 0, + "batch_size": 32, + "learning_rate": 0.0001, + "adam_b1": 0.8, + "adam_b2": 0.99, + "lr_decay": 0.9999996, + "seed": 1234, + + "upsample_rates": [8,8,2,2], + "upsample_kernel_sizes": [16,16,4,4], + "upsample_initial_channel": 512, + "resblock_kernel_sizes": [3,7,11], + "resblock_dilation_sizes": [[1,3,5], [1,3,5], [1,3,5]], + + "activation": "snakebeta", + "snake_logscale": true, + + "resolutions": [[1024, 120, 600], [2048, 240, 1200], [512, 50, 240]], + "mpd_reshapes": [2, 3, 5, 7, 11], + "use_spectral_norm": false, + "discriminator_channel_mult": 1, + + "segment_size": 8192, + "num_mels": 100, + "num_freq": 1025, + "n_fft": 1024, + "hop_size": 256, + "win_size": 1024, + + "sampling_rate": 24000, + + "fmin": 0, + "fmax": 12000, + "fmax_for_loss": null, + + "num_workers": 4, + + "dist_config": { + "dist_backend": "nccl", + "dist_url": "tcp://localhost:54321", + "world_size": 1 + } +} diff --git a/src/third_party/BigVGAN/configs/bigvgan_v2_22khz_80band_256x.json b/src/third_party/BigVGAN/configs/bigvgan_v2_22khz_80band_256x.json new file mode 100644 index 0000000000000000000000000000000000000000..8174afe0eef575d201ce6e1aeab1f94ee1490450 --- /dev/null +++ b/src/third_party/BigVGAN/configs/bigvgan_v2_22khz_80band_256x.json @@ -0,0 +1,61 @@ +{ + "resblock": "1", + "num_gpus": 0, + "batch_size": 4, + "learning_rate": 0.0001, + "adam_b1": 0.8, + "adam_b2": 0.99, + "lr_decay": 0.9999996, + "seed": 1234, + + "upsample_rates": [4,4,2,2,2,2], + "upsample_kernel_sizes": [8,8,4,4,4,4], + "upsample_initial_channel": 1536, + "resblock_kernel_sizes": [3,7,11], + "resblock_dilation_sizes": [[1,3,5], [1,3,5], [1,3,5]], + + "use_tanh_at_final": false, + "use_bias_at_final": false, + + "activation": "snakebeta", + "snake_logscale": true, + + "use_cqtd_instead_of_mrd": true, + "cqtd_filters": 128, + "cqtd_max_filters": 1024, + "cqtd_filters_scale": 1, + "cqtd_dilations": [1, 2, 4], + "cqtd_hop_lengths": [512, 256, 256], + "cqtd_n_octaves": [9, 9, 9], + "cqtd_bins_per_octaves": [24, 36, 48], + + "mpd_reshapes": [2, 3, 5, 7, 11], + "use_spectral_norm": false, + "discriminator_channel_mult": 1, + + "use_multiscale_melloss": true, + "lambda_melloss": 15, + + "clip_grad_norm": 500, + + "segment_size": 65536, + "num_mels": 80, + "num_freq": 1025, + "n_fft": 1024, + "hop_size": 256, + "win_size": 1024, + + "sampling_rate": 22050, + + "fmin": 0, + "fmax": null, + "fmax_for_loss": null, + + "num_workers": 4, + + "dist_config": { + "dist_backend": "nccl", + "dist_url": "tcp://localhost:54321", + "world_size": 1 + } +} diff --git a/src/third_party/BigVGAN/configs/bigvgan_v2_22khz_80band_fmax8k_256x.json b/src/third_party/BigVGAN/configs/bigvgan_v2_22khz_80band_fmax8k_256x.json new file mode 100644 index 0000000000000000000000000000000000000000..f00a0c942326b4153e281c061714c23b8eb9b2d7 --- /dev/null +++ b/src/third_party/BigVGAN/configs/bigvgan_v2_22khz_80band_fmax8k_256x.json @@ -0,0 +1,61 @@ +{ + "resblock": "1", + "num_gpus": 0, + "batch_size": 4, + "learning_rate": 0.0001, + "adam_b1": 0.8, + "adam_b2": 0.99, + "lr_decay": 0.9999996, + "seed": 1234, + + "upsample_rates": [4,4,2,2,2,2], + "upsample_kernel_sizes": [8,8,4,4,4,4], + "upsample_initial_channel": 1536, + "resblock_kernel_sizes": [3,7,11], + "resblock_dilation_sizes": [[1,3,5], [1,3,5], [1,3,5]], + + "use_tanh_at_final": false, + "use_bias_at_final": false, + + "activation": "snakebeta", + "snake_logscale": true, + + "use_cqtd_instead_of_mrd": true, + "cqtd_filters": 128, + "cqtd_max_filters": 1024, + "cqtd_filters_scale": 1, + "cqtd_dilations": [1, 2, 4], + "cqtd_hop_lengths": [512, 256, 256], + "cqtd_n_octaves": [9, 9, 9], + "cqtd_bins_per_octaves": [24, 36, 48], + + "mpd_reshapes": [2, 3, 5, 7, 11], + "use_spectral_norm": false, + "discriminator_channel_mult": 1, + + "use_multiscale_melloss": true, + "lambda_melloss": 15, + + "clip_grad_norm": 500, + + "segment_size": 65536, + "num_mels": 80, + "num_freq": 1025, + "n_fft": 1024, + "hop_size": 256, + "win_size": 1024, + + "sampling_rate": 22050, + + "fmin": 0, + "fmax": 8000, + "fmax_for_loss": null, + + "num_workers": 4, + + "dist_config": { + "dist_backend": "nccl", + "dist_url": "tcp://localhost:54321", + "world_size": 1 + } +} diff --git a/src/third_party/BigVGAN/configs/bigvgan_v2_24khz_100band_256x.json b/src/third_party/BigVGAN/configs/bigvgan_v2_24khz_100band_256x.json new file mode 100644 index 0000000000000000000000000000000000000000..072c12d3968baaaa2c49da917a0ded18b22ce648 --- /dev/null +++ b/src/third_party/BigVGAN/configs/bigvgan_v2_24khz_100band_256x.json @@ -0,0 +1,61 @@ +{ + "resblock": "1", + "num_gpus": 0, + "batch_size": 4, + "learning_rate": 0.0001, + "adam_b1": 0.8, + "adam_b2": 0.99, + "lr_decay": 0.9999996, + "seed": 1234, + + "upsample_rates": [4,4,2,2,2,2], + "upsample_kernel_sizes": [8,8,4,4,4,4], + "upsample_initial_channel": 1536, + "resblock_kernel_sizes": [3,7,11], + "resblock_dilation_sizes": [[1,3,5], [1,3,5], [1,3,5]], + + "use_tanh_at_final": false, + "use_bias_at_final": false, + + "activation": "snakebeta", + "snake_logscale": true, + + "use_cqtd_instead_of_mrd": true, + "cqtd_filters": 128, + "cqtd_max_filters": 1024, + "cqtd_filters_scale": 1, + "cqtd_dilations": [1, 2, 4], + "cqtd_hop_lengths": [512, 256, 256], + "cqtd_n_octaves": [9, 9, 9], + "cqtd_bins_per_octaves": [24, 36, 48], + + "mpd_reshapes": [2, 3, 5, 7, 11], + "use_spectral_norm": false, + "discriminator_channel_mult": 1, + + "use_multiscale_melloss": true, + "lambda_melloss": 15, + + "clip_grad_norm": 500, + + "segment_size": 65536, + "num_mels": 100, + "num_freq": 1025, + "n_fft": 1024, + "hop_size": 256, + "win_size": 1024, + + "sampling_rate": 24000, + + "fmin": 0, + "fmax": null, + "fmax_for_loss": null, + + "num_workers": 4, + + "dist_config": { + "dist_backend": "nccl", + "dist_url": "tcp://localhost:54321", + "world_size": 1 + } +} diff --git a/src/third_party/BigVGAN/configs/bigvgan_v2_44khz_128band_256x.json b/src/third_party/BigVGAN/configs/bigvgan_v2_44khz_128band_256x.json new file mode 100644 index 0000000000000000000000000000000000000000..ea536992e87ee9cd1a7e66847d2e0fd0fe4cf1f5 --- /dev/null +++ b/src/third_party/BigVGAN/configs/bigvgan_v2_44khz_128band_256x.json @@ -0,0 +1,61 @@ +{ + "resblock": "1", + "num_gpus": 0, + "batch_size": 4, + "learning_rate": 0.0001, + "adam_b1": 0.8, + "adam_b2": 0.99, + "lr_decay": 0.9999996, + "seed": 1234, + + "upsample_rates": [4,4,2,2,2,2], + "upsample_kernel_sizes": [8,8,4,4,4,4], + "upsample_initial_channel": 1536, + "resblock_kernel_sizes": [3,7,11], + "resblock_dilation_sizes": [[1,3,5], [1,3,5], [1,3,5]], + + "use_tanh_at_final": false, + "use_bias_at_final": false, + + "activation": "snakebeta", + "snake_logscale": true, + + "use_cqtd_instead_of_mrd": true, + "cqtd_filters": 128, + "cqtd_max_filters": 1024, + "cqtd_filters_scale": 1, + "cqtd_dilations": [1, 2, 4], + "cqtd_hop_lengths": [512, 256, 256], + "cqtd_n_octaves": [9, 9, 9], + "cqtd_bins_per_octaves": [24, 36, 48], + + "mpd_reshapes": [2, 3, 5, 7, 11], + "use_spectral_norm": false, + "discriminator_channel_mult": 1, + + "use_multiscale_melloss": true, + "lambda_melloss": 15, + + "clip_grad_norm": 500, + + "segment_size": 65536, + "num_mels": 128, + "num_freq": 1025, + "n_fft": 1024, + "hop_size": 256, + "win_size": 1024, + + "sampling_rate": 44100, + + "fmin": 0, + "fmax": null, + "fmax_for_loss": null, + + "num_workers": 4, + + "dist_config": { + "dist_backend": "nccl", + "dist_url": "tcp://localhost:54321", + "world_size": 1 + } +} diff --git a/src/third_party/BigVGAN/configs/bigvgan_v2_44khz_128band_512x.json b/src/third_party/BigVGAN/configs/bigvgan_v2_44khz_128band_512x.json new file mode 100644 index 0000000000000000000000000000000000000000..866e130b3c26902e9931e0fe8bd74602ffc2cdbf --- /dev/null +++ b/src/third_party/BigVGAN/configs/bigvgan_v2_44khz_128band_512x.json @@ -0,0 +1,61 @@ +{ + "resblock": "1", + "num_gpus": 0, + "batch_size": 4, + "learning_rate": 0.0001, + "adam_b1": 0.8, + "adam_b2": 0.99, + "lr_decay": 0.9999996, + "seed": 1234, + + "upsample_rates": [8,4,2,2,2,2], + "upsample_kernel_sizes": [16,8,4,4,4,4], + "upsample_initial_channel": 1536, + "resblock_kernel_sizes": [3,7,11], + "resblock_dilation_sizes": [[1,3,5], [1,3,5], [1,3,5]], + + "use_tanh_at_final": false, + "use_bias_at_final": false, + + "activation": "snakebeta", + "snake_logscale": true, + + "use_cqtd_instead_of_mrd": true, + "cqtd_filters": 128, + "cqtd_max_filters": 1024, + "cqtd_filters_scale": 1, + "cqtd_dilations": [1, 2, 4], + "cqtd_hop_lengths": [512, 256, 256], + "cqtd_n_octaves": [9, 9, 9], + "cqtd_bins_per_octaves": [24, 36, 48], + + "mpd_reshapes": [2, 3, 5, 7, 11], + "use_spectral_norm": false, + "discriminator_channel_mult": 1, + + "use_multiscale_melloss": true, + "lambda_melloss": 15, + + "clip_grad_norm": 500, + + "segment_size": 65536, + "num_mels": 128, + "num_freq": 2049, + "n_fft": 2048, + "hop_size": 512, + "win_size": 2048, + + "sampling_rate": 44100, + + "fmin": 0, + "fmax": null, + "fmax_for_loss": null, + + "num_workers": 4, + + "dist_config": { + "dist_backend": "nccl", + "dist_url": "tcp://localhost:54321", + "world_size": 1 + } +} diff --git a/src/third_party/BigVGAN/demo/__init__.py b/src/third_party/BigVGAN/demo/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/src/third_party/BigVGAN/demo/app.py b/src/third_party/BigVGAN/demo/app.py new file mode 100644 index 0000000000000000000000000000000000000000..f1436f8ea57758660a87cb8c22f0fbde3fbc7127 --- /dev/null +++ b/src/third_party/BigVGAN/demo/app.py @@ -0,0 +1,441 @@ +# Copyright (c) 2024 NVIDIA CORPORATION. +# Licensed under the MIT license. + +import spaces +import gradio as gr +import pandas as pd +import torch +import os +import sys + +# to import modules from parent_dir +parent_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), "..")) +sys.path.append(parent_dir) + +from meldataset import get_mel_spectrogram, MAX_WAV_VALUE +from bigvgan import BigVGAN +import librosa +import numpy as np +from utils import plot_spectrogram +import PIL + +if torch.cuda.is_available(): + device = torch.device("cuda") + torch.backends.cudnn.benchmark = False + print(f"using GPU") +else: + device = torch.device("cpu") + print(f"using CPU") + + +def inference_gradio(input, model_choice): # Input is audio waveform in [T, channel] + sr, audio = input # Unpack input to sampling rate and audio itself + audio = np.transpose(audio) # Transpose to [channel, T] for librosa + audio = audio / MAX_WAV_VALUE # Convert int16 to float range used by BigVGAN + + model = dict_model[model_choice] + + if sr != model.h.sampling_rate: # Convert audio to model's sampling rate + audio = librosa.resample(audio, orig_sr=sr, target_sr=model.h.sampling_rate) + if len(audio.shape) == 2: # Stereo + audio = librosa.to_mono(audio) # Convert to mono if stereo + audio = librosa.util.normalize(audio) * 0.95 + + output, spec_gen = inference_model( + audio, model + ) # Output is generated audio in ndarray, int16 + + spec_plot_gen = plot_spectrogram(spec_gen) + + output_audio = (model.h.sampling_rate, output) # Tuple for gr.Audio output + + buffer = spec_plot_gen.canvas.buffer_rgba() + output_image = PIL.Image.frombuffer( + "RGBA", spec_plot_gen.canvas.get_width_height(), buffer, "raw", "RGBA", 0, 1 + ) + + return output_audio, output_image + + +@spaces.GPU(duration=120) +def inference_model(audio_input, model): + # Load model to device + model.to(device) + + with torch.inference_mode(): + wav = torch.FloatTensor(audio_input) + # Compute mel spectrogram from the ground truth audio + spec_gt = get_mel_spectrogram(wav.unsqueeze(0), model.h).to(device) + + y_g_hat = model(spec_gt) + + audio_gen = y_g_hat.squeeze().cpu() + spec_gen = get_mel_spectrogram(audio_gen.unsqueeze(0), model.h) + audio_gen = audio_gen.numpy() # [T], float [-1, 1] + audio_gen = (audio_gen * MAX_WAV_VALUE).astype("int16") # [T], int16 + spec_gen = spec_gen.squeeze().numpy() # [C, T_frame] + + # Unload to CPU + model.to("cpu") + # Delete GPU tensor + del spec_gt, y_g_hat + + return audio_gen, spec_gen + + +css = """ + a { + color: inherit; + text-decoration: underline; + } + .gradio-container { + font-family: 'IBM Plex Sans', sans-serif; + } + .gr-button { + color: white; + border-color: #000000; + background: #000000; + } + input[type='range'] { + accent-color: #000000; + } + .dark input[type='range'] { + accent-color: #dfdfdf; + } + .container { + max-width: 730px; + margin: auto; + padding-top: 1.5rem; + } + #gallery { + min-height: 22rem; + margin-bottom: 15px; + margin-left: auto; + margin-right: auto; + border-bottom-right-radius: .5rem !important; + border-bottom-left-radius: .5rem !important; + } + #gallery>div>.h-full { + min-height: 20rem; + } + .details:hover { + text-decoration: underline; + } + .gr-button { + white-space: nowrap; + } + .gr-button:focus { + border-color: rgb(147 197 253 / var(--tw-border-opacity)); + outline: none; + box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000); + --tw-border-opacity: 1; + --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color); + --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(3px var(--tw-ring-offset-width)) var(--tw-ring-color); + --tw-ring-color: rgb(191 219 254 / var(--tw-ring-opacity)); + --tw-ring-opacity: .5; + } + #advanced-btn { + font-size: .7rem !important; + line-height: 19px; + margin-top: 12px; + margin-bottom: 12px; + padding: 2px 8px; + border-radius: 14px !important; + } + #advanced-options { + margin-bottom: 20px; + } + .footer { + margin-bottom: 45px; + margin-top: 35px; + text-align: center; + border-bottom: 1px solid #e5e5e5; + } + .footer>p { + font-size: .8rem; + display: inline-block; + padding: 0 10px; + transform: translateY(10px); + background: white; + } + .dark .footer { + border-color: #303030; + } + .dark .footer>p { + background: #0b0f19; + } + .acknowledgments h4{ + margin: 1.25em 0 .25em 0; + font-weight: bold; + font-size: 115%; + } + #container-advanced-btns{ + display: flex; + flex-wrap: wrap; + justify-content: space-between; + align-items: center; + } + .animate-spin { + animation: spin 1s linear infinite; + } + @keyframes spin { + from { + transform: rotate(0deg); + } + to { + transform: rotate(360deg); + } + } + #share-btn-container { + display: flex; padding-left: 0.5rem !important; padding-right: 0.5rem !important; background-color: #000000; justify-content: center; align-items: center; border-radius: 9999px !important; width: 13rem; + margin-top: 10px; + margin-left: auto; + } + #share-btn { + all: initial; color: #ffffff;font-weight: 600; cursor:pointer; font-family: 'IBM Plex Sans', sans-serif; margin-left: 0.5rem !important; padding-top: 0.25rem !important; padding-bottom: 0.25rem !important;right:0; + } + #share-btn * { + all: unset; + } + #share-btn-container div:nth-child(-n+2){ + width: auto !important; + min-height: 0px !important; + } + #share-btn-container .wrap { + display: none !important; + } + .gr-form{ + flex: 1 1 50%; border-top-right-radius: 0; border-bottom-right-radius: 0; + } + #prompt-container{ + gap: 0; + } + #generated_id{ + min-height: 700px + } + #setting_id{ + margin-bottom: 12px; + text-align: center; + font-weight: 900; + } +""" + +# Script for loading the models + +LIST_MODEL_ID = [ + "bigvgan_24khz_100band", + "bigvgan_base_24khz_100band", + "bigvgan_22khz_80band", + "bigvgan_base_22khz_80band", + "bigvgan_v2_22khz_80band_256x", + "bigvgan_v2_22khz_80band_fmax8k_256x", + "bigvgan_v2_24khz_100band_256x", + "bigvgan_v2_44khz_128band_256x", + "bigvgan_v2_44khz_128band_512x", +] + +dict_model = {} +dict_config = {} + +for model_name in LIST_MODEL_ID: + + generator = BigVGAN.from_pretrained("nvidia/" + model_name) + generator.remove_weight_norm() + generator.eval() + + dict_model[model_name] = generator + dict_config[model_name] = generator.h + +# Script for Gradio UI + +iface = gr.Blocks(css=css, title="BigVGAN - Demo") + +with iface: + gr.HTML( + """ +
+
+

+ BigVGAN: A Universal Neural Vocoder with Large-Scale Training +

+
+

+ [Paper] [Code] [Demo] [Project page] +

+
+ """ + ) + gr.HTML( + """ +
+

News

+

[Jul 2024] We release BigVGAN-v2 along with pretrained checkpoints. Below are the highlights:

+
    +
  • Custom CUDA kernel for inference: we provide a fused upsampling + activation kernel written in CUDA for accelerated inference speed. Our test shows 1.5 - 3x faster speed on a single A100 GPU.
  • +
  • Improved discriminator and loss: BigVGAN-v2 is trained using a multi-scale sub-band CQT discriminator and a multi-scale mel spectrogram loss.
  • +
  • Larger training data: BigVGAN-v2 is trained using datasets containing diverse audio types, including speech in multiple languages, environmental sounds, and instruments.
  • +
  • We provide pretrained checkpoints of BigVGAN-v2 using diverse audio configurations, supporting up to 44 kHz sampling rate and 512x upsampling ratio. See the table below for the link.
  • +
+
+ """ + ) + gr.HTML( + """ +
+

Model Overview

+ BigVGAN is a universal neural vocoder model that generates audio waveforms using mel spectrogram as inputs. +
+
+ """ + ) + with gr.Accordion("Input"): + + model_choice = gr.Dropdown( + label="Select the model to use", + info="The default model is bigvgan_v2_24khz_100band_256x", + value="bigvgan_v2_24khz_100band_256x", + choices=[m for m in LIST_MODEL_ID], + interactive=True, + ) + + audio_input = gr.Audio( + label="Input Audio", elem_id="input-audio", interactive=True + ) + + button = gr.Button("Submit") + + with gr.Accordion("Output"): + with gr.Column(): + output_audio = gr.Audio(label="Output Audio", elem_id="output-audio") + output_image = gr.Image( + label="Output Mel Spectrogram", elem_id="output-image-gen" + ) + + button.click( + inference_gradio, + inputs=[audio_input, model_choice], + outputs=[output_audio, output_image], + concurrency_limit=10, + ) + + gr.Examples( + [ + [ + os.path.join(os.path.dirname(__file__), "examples/jensen_24k.wav"), + "bigvgan_v2_24khz_100band_256x", + ], + [ + os.path.join(os.path.dirname(__file__), "examples/libritts_24k.wav"), + "bigvgan_v2_24khz_100band_256x", + ], + [ + os.path.join(os.path.dirname(__file__), "examples/queen_24k.wav"), + "bigvgan_v2_24khz_100band_256x", + ], + [ + os.path.join(os.path.dirname(__file__), "examples/dance_24k.wav"), + "bigvgan_v2_24khz_100band_256x", + ], + [ + os.path.join(os.path.dirname(__file__), "examples/megalovania_24k.wav"), + "bigvgan_v2_24khz_100band_256x", + ], + [ + os.path.join(os.path.dirname(__file__), "examples/hifitts_44k.wav"), + "bigvgan_v2_44khz_128band_256x", + ], + [ + os.path.join(os.path.dirname(__file__), "examples/musdbhq_44k.wav"), + "bigvgan_v2_44khz_128band_256x", + ], + [ + os.path.join(os.path.dirname(__file__), "examples/musiccaps1_44k.wav"), + "bigvgan_v2_44khz_128band_256x", + ], + [ + os.path.join(os.path.dirname(__file__), "examples/musiccaps2_44k.wav"), + "bigvgan_v2_44khz_128band_256x", + ], + ], + fn=inference_gradio, + inputs=[audio_input, model_choice], + outputs=[output_audio, output_image], + ) + + # Define the data for the table + data = { + "Model Name": [ + "bigvgan_v2_44khz_128band_512x", + "bigvgan_v2_44khz_128band_256x", + "bigvgan_v2_24khz_100band_256x", + "bigvgan_v2_22khz_80band_256x", + "bigvgan_v2_22khz_80band_fmax8k_256x", + "bigvgan_24khz_100band", + "bigvgan_base_24khz_100band", + "bigvgan_22khz_80band", + "bigvgan_base_22khz_80band", + ], + "Sampling Rate": [ + "44 kHz", + "44 kHz", + "24 kHz", + "22 kHz", + "22 kHz", + "24 kHz", + "24 kHz", + "22 kHz", + "22 kHz", + ], + "Mel band": [128, 128, 100, 80, 80, 100, 100, 80, 80], + "fmax": [22050, 22050, 12000, 11025, 8000, 12000, 12000, 8000, 8000], + "Upsampling Ratio": [512, 256, 256, 256, 256, 256, 256, 256, 256], + "Parameters": [ + "122M", + "112M", + "112M", + "112M", + "112M", + "112M", + "14M", + "112M", + "14M", + ], + "Dataset": [ + "Large-scale Compilation", + "Large-scale Compilation", + "Large-scale Compilation", + "Large-scale Compilation", + "Large-scale Compilation", + "LibriTTS", + "LibriTTS", + "LibriTTS + VCTK + LJSpeech", + "LibriTTS + VCTK + LJSpeech", + ], + "Fine-Tuned": ["No", "No", "No", "No", "No", "No", "No", "No", "No"], + } + + base_url = "https://huggingface.co/nvidia/" + + df = pd.DataFrame(data) + df["Model Name"] = df["Model Name"].apply( + lambda x: f'{x}' + ) + + html_table = gr.HTML( + f""" +
+ {df.to_html(index=False, escape=False, classes='border="1" cellspacing="0" cellpadding="5" style="margin-left: auto; margin-right: auto;')} +

NOTE: The v1 models are trained using speech audio datasets ONLY! (24kHz models: LibriTTS, 22kHz models: LibriTTS + VCTK + LJSpeech).

+
+ """ + ) + +iface.queue() +iface.launch() diff --git a/src/third_party/BigVGAN/demo/examples/dance_24k.wav b/src/third_party/BigVGAN/demo/examples/dance_24k.wav new file mode 100644 index 0000000000000000000000000000000000000000..01e2e52e3138e1e497b9c0e2ecd0617e0aa91a9c --- /dev/null +++ b/src/third_party/BigVGAN/demo/examples/dance_24k.wav @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7068d78ce4d008a793f6bfbbe49d0f8962a752f07780833c5ab73652da9849fd +size 479788 diff --git a/src/third_party/BigVGAN/demo/examples/hifitts_44k.wav b/src/third_party/BigVGAN/demo/examples/hifitts_44k.wav new file mode 100644 index 0000000000000000000000000000000000000000..a7866d10df71489d7a938590dedba980dab4aeb8 --- /dev/null +++ b/src/third_party/BigVGAN/demo/examples/hifitts_44k.wav @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:01f7653b188bdb7349542bbc8af473208d463639682b684527cef651d8225483 +size 570024 diff --git a/src/third_party/BigVGAN/demo/examples/jensen_24k.wav b/src/third_party/BigVGAN/demo/examples/jensen_24k.wav new file mode 100644 index 0000000000000000000000000000000000000000..4b8e6c007eda447d6726eccabb2eac19072f9750 --- /dev/null +++ b/src/third_party/BigVGAN/demo/examples/jensen_24k.wav @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8ec26c78377e056ba8f08e0c337cc535c0fe08a9d0e7923ef3f5c52369173713 +size 479788 diff --git a/src/third_party/BigVGAN/demo/examples/libritts_24k.wav b/src/third_party/BigVGAN/demo/examples/libritts_24k.wav new file mode 100644 index 0000000000000000000000000000000000000000..b4a74e9fb4d59b1607728a6575f789393d1a4c6f --- /dev/null +++ b/src/third_party/BigVGAN/demo/examples/libritts_24k.wav @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4e9259975995438846da86fd69f0263a1ef859a6e5a4c4501b7c71bca52d5acc +size 281644 diff --git a/src/third_party/BigVGAN/demo/examples/megalovania_24k.wav b/src/third_party/BigVGAN/demo/examples/megalovania_24k.wav new file mode 100644 index 0000000000000000000000000000000000000000..593127d88b4d829a4fd91ede0972db20eac86766 --- /dev/null +++ b/src/third_party/BigVGAN/demo/examples/megalovania_24k.wav @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7970ac637e680876d48ad84e9185db1b21da01929fe46d855e8794bd83d14c20 +size 1548328 diff --git a/src/third_party/BigVGAN/demo/examples/musdbhq_44k.wav b/src/third_party/BigVGAN/demo/examples/musdbhq_44k.wav new file mode 100644 index 0000000000000000000000000000000000000000..f8251c3ad9a3c9550c07595380fcb33d56cd1d31 --- /dev/null +++ b/src/third_party/BigVGAN/demo/examples/musdbhq_44k.wav @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:87dbdabc47550f493c2c0e2c9389b6dddffb93977408b54d9c4db3b5f071856c +size 917548 diff --git a/src/third_party/BigVGAN/demo/examples/musiccaps1_44k.wav b/src/third_party/BigVGAN/demo/examples/musiccaps1_44k.wav new file mode 100644 index 0000000000000000000000000000000000000000..d0a53e64f42115e8fe6cc6c295fb40baeb5006c6 --- /dev/null +++ b/src/third_party/BigVGAN/demo/examples/musiccaps1_44k.wav @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d433e0be92a742e9fd2c6a38d627e8cf8864c78ba76f334bd99ec9d931fb615f +size 887062 diff --git a/src/third_party/BigVGAN/demo/examples/musiccaps2_44k.wav b/src/third_party/BigVGAN/demo/examples/musiccaps2_44k.wav new file mode 100644 index 0000000000000000000000000000000000000000..3cd583be8912e6de1fc1939faf64e6d388834a54 --- /dev/null +++ b/src/third_party/BigVGAN/demo/examples/musiccaps2_44k.wav @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0fafab98d1d31866e432c6b5cfd67e19278ce5a37547781c30c5638136cbab04 +size 887062 diff --git a/src/third_party/BigVGAN/demo/examples/queen_24k.wav b/src/third_party/BigVGAN/demo/examples/queen_24k.wav new file mode 100644 index 0000000000000000000000000000000000000000..e734078da2f90b4c8615793a13ecb4219d9c02d7 --- /dev/null +++ b/src/third_party/BigVGAN/demo/examples/queen_24k.wav @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ee9fcaf8d21b098f94541b6f2dfc0803167b39f2aea0ca5c40d0b7430b3954d8 +size 479788 diff --git a/src/third_party/BigVGAN/demo/requirements.txt b/src/third_party/BigVGAN/demo/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..f189bcd63b8259be23d163e3848b585406059688 --- /dev/null +++ b/src/third_party/BigVGAN/demo/requirements.txt @@ -0,0 +1,15 @@ +torch +numpy +librosa>=0.8.1 +scipy +tensorboard +soundfile +matplotlib +pesq +auraloss +tqdm +nnAudio +ninja +huggingface_hub>=0.23.4 +gradio>=4.38.1 +spaces>=0.28.3 \ No newline at end of file diff --git a/src/third_party/BigVGAN/discriminators.py b/src/third_party/BigVGAN/discriminators.py new file mode 100644 index 0000000000000000000000000000000000000000..197f6ff77e8040e1aad8ce1ffe7c871653dbb0d8 --- /dev/null +++ b/src/third_party/BigVGAN/discriminators.py @@ -0,0 +1,651 @@ +# Copyright (c) 2024 NVIDIA CORPORATION. +# Licensed under the MIT license. + +# Adapted from https://github.com/jik876/hifi-gan under the MIT license. +# LICENSE is in incl_licenses directory. + + +import torch +import torch.nn.functional as F +import torch.nn as nn +from torch.nn import Conv2d +from torch.nn.utils import weight_norm, spectral_norm +from torchaudio.transforms import Spectrogram, Resample + +from env import AttrDict +from utils import get_padding +import typing +from typing import Optional, List, Union, Dict, Tuple + + +class DiscriminatorP(torch.nn.Module): + def __init__( + self, + h: AttrDict, + period: List[int], + kernel_size: int = 5, + stride: int = 3, + use_spectral_norm: bool = False, + ): + super().__init__() + self.period = period + self.d_mult = h.discriminator_channel_mult + norm_f = weight_norm if not use_spectral_norm else spectral_norm + + self.convs = nn.ModuleList( + [ + norm_f( + Conv2d( + 1, + int(32 * self.d_mult), + (kernel_size, 1), + (stride, 1), + padding=(get_padding(5, 1), 0), + ) + ), + norm_f( + Conv2d( + int(32 * self.d_mult), + int(128 * self.d_mult), + (kernel_size, 1), + (stride, 1), + padding=(get_padding(5, 1), 0), + ) + ), + norm_f( + Conv2d( + int(128 * self.d_mult), + int(512 * self.d_mult), + (kernel_size, 1), + (stride, 1), + padding=(get_padding(5, 1), 0), + ) + ), + norm_f( + Conv2d( + int(512 * self.d_mult), + int(1024 * self.d_mult), + (kernel_size, 1), + (stride, 1), + padding=(get_padding(5, 1), 0), + ) + ), + norm_f( + Conv2d( + int(1024 * self.d_mult), + int(1024 * self.d_mult), + (kernel_size, 1), + 1, + padding=(2, 0), + ) + ), + ] + ) + self.conv_post = norm_f( + Conv2d(int(1024 * self.d_mult), 1, (3, 1), 1, padding=(1, 0)) + ) + + def forward(self, x: torch.Tensor) -> Tuple[torch.Tensor, List[torch.Tensor]]: + fmap = [] + + # 1d to 2d + b, c, t = x.shape + if t % self.period != 0: # pad first + n_pad = self.period - (t % self.period) + x = F.pad(x, (0, n_pad), "reflect") + t = t + n_pad + x = x.view(b, c, t // self.period, self.period) + + for l in self.convs: + x = l(x) + x = F.leaky_relu(x, 0.1) + fmap.append(x) + x = self.conv_post(x) + fmap.append(x) + x = torch.flatten(x, 1, -1) + + return x, fmap + + +class MultiPeriodDiscriminator(torch.nn.Module): + def __init__(self, h: AttrDict): + super().__init__() + self.mpd_reshapes = h.mpd_reshapes + print(f"mpd_reshapes: {self.mpd_reshapes}") + self.discriminators = nn.ModuleList( + [ + DiscriminatorP(h, rs, use_spectral_norm=h.use_spectral_norm) + for rs in self.mpd_reshapes + ] + ) + + def forward(self, y: torch.Tensor, y_hat: torch.Tensor) -> Tuple[ + List[torch.Tensor], + List[torch.Tensor], + List[List[torch.Tensor]], + List[List[torch.Tensor]], + ]: + y_d_rs = [] + y_d_gs = [] + fmap_rs = [] + fmap_gs = [] + for i, d in enumerate(self.discriminators): + y_d_r, fmap_r = d(y) + y_d_g, fmap_g = d(y_hat) + y_d_rs.append(y_d_r) + fmap_rs.append(fmap_r) + y_d_gs.append(y_d_g) + fmap_gs.append(fmap_g) + + return y_d_rs, y_d_gs, fmap_rs, fmap_gs + + +class DiscriminatorR(nn.Module): + def __init__(self, cfg: AttrDict, resolution: List[List[int]]): + super().__init__() + + self.resolution = resolution + assert ( + len(self.resolution) == 3 + ), f"MRD layer requires list with len=3, got {self.resolution}" + self.lrelu_slope = 0.1 + + norm_f = weight_norm if cfg.use_spectral_norm == False else spectral_norm + if hasattr(cfg, "mrd_use_spectral_norm"): + print( + f"[INFO] overriding MRD use_spectral_norm as {cfg.mrd_use_spectral_norm}" + ) + norm_f = ( + weight_norm if cfg.mrd_use_spectral_norm == False else spectral_norm + ) + self.d_mult = cfg.discriminator_channel_mult + if hasattr(cfg, "mrd_channel_mult"): + print(f"[INFO] overriding mrd channel multiplier as {cfg.mrd_channel_mult}") + self.d_mult = cfg.mrd_channel_mult + + self.convs = nn.ModuleList( + [ + norm_f(nn.Conv2d(1, int(32 * self.d_mult), (3, 9), padding=(1, 4))), + norm_f( + nn.Conv2d( + int(32 * self.d_mult), + int(32 * self.d_mult), + (3, 9), + stride=(1, 2), + padding=(1, 4), + ) + ), + norm_f( + nn.Conv2d( + int(32 * self.d_mult), + int(32 * self.d_mult), + (3, 9), + stride=(1, 2), + padding=(1, 4), + ) + ), + norm_f( + nn.Conv2d( + int(32 * self.d_mult), + int(32 * self.d_mult), + (3, 9), + stride=(1, 2), + padding=(1, 4), + ) + ), + norm_f( + nn.Conv2d( + int(32 * self.d_mult), + int(32 * self.d_mult), + (3, 3), + padding=(1, 1), + ) + ), + ] + ) + self.conv_post = norm_f( + nn.Conv2d(int(32 * self.d_mult), 1, (3, 3), padding=(1, 1)) + ) + + def forward(self, x: torch.Tensor) -> Tuple[torch.Tensor, List[torch.Tensor]]: + fmap = [] + + x = self.spectrogram(x) + x = x.unsqueeze(1) + for l in self.convs: + x = l(x) + x = F.leaky_relu(x, self.lrelu_slope) + fmap.append(x) + x = self.conv_post(x) + fmap.append(x) + x = torch.flatten(x, 1, -1) + + return x, fmap + + def spectrogram(self, x: torch.Tensor) -> torch.Tensor: + n_fft, hop_length, win_length = self.resolution + x = F.pad( + x, + (int((n_fft - hop_length) / 2), int((n_fft - hop_length) / 2)), + mode="reflect", + ) + x = x.squeeze(1) + x = torch.stft( + x, + n_fft=n_fft, + hop_length=hop_length, + win_length=win_length, + center=False, + return_complex=True, + ) + x = torch.view_as_real(x) # [B, F, TT, 2] + mag = torch.norm(x, p=2, dim=-1) # [B, F, TT] + + return mag + + +class MultiResolutionDiscriminator(nn.Module): + def __init__(self, cfg, debug=False): + super().__init__() + self.resolutions = cfg.resolutions + assert ( + len(self.resolutions) == 3 + ), f"MRD requires list of list with len=3, each element having a list with len=3. Got {self.resolutions}" + self.discriminators = nn.ModuleList( + [DiscriminatorR(cfg, resolution) for resolution in self.resolutions] + ) + + def forward(self, y: torch.Tensor, y_hat: torch.Tensor) -> Tuple[ + List[torch.Tensor], + List[torch.Tensor], + List[List[torch.Tensor]], + List[List[torch.Tensor]], + ]: + y_d_rs = [] + y_d_gs = [] + fmap_rs = [] + fmap_gs = [] + + for i, d in enumerate(self.discriminators): + y_d_r, fmap_r = d(x=y) + y_d_g, fmap_g = d(x=y_hat) + y_d_rs.append(y_d_r) + fmap_rs.append(fmap_r) + y_d_gs.append(y_d_g) + fmap_gs.append(fmap_g) + + return y_d_rs, y_d_gs, fmap_rs, fmap_gs + + +# Method based on descript-audio-codec: https://github.com/descriptinc/descript-audio-codec +# Modified code adapted from https://github.com/gemelo-ai/vocos under the MIT license. +# LICENSE is in incl_licenses directory. +class DiscriminatorB(nn.Module): + def __init__( + self, + window_length: int, + channels: int = 32, + hop_factor: float = 0.25, + bands: Tuple[Tuple[float, float], ...] = ( + (0.0, 0.1), + (0.1, 0.25), + (0.25, 0.5), + (0.5, 0.75), + (0.75, 1.0), + ), + ): + super().__init__() + self.window_length = window_length + self.hop_factor = hop_factor + self.spec_fn = Spectrogram( + n_fft=window_length, + hop_length=int(window_length * hop_factor), + win_length=window_length, + power=None, + ) + n_fft = window_length // 2 + 1 + bands = [(int(b[0] * n_fft), int(b[1] * n_fft)) for b in bands] + self.bands = bands + convs = lambda: nn.ModuleList( + [ + weight_norm(nn.Conv2d(2, channels, (3, 9), (1, 1), padding=(1, 4))), + weight_norm( + nn.Conv2d(channels, channels, (3, 9), (1, 2), padding=(1, 4)) + ), + weight_norm( + nn.Conv2d(channels, channels, (3, 9), (1, 2), padding=(1, 4)) + ), + weight_norm( + nn.Conv2d(channels, channels, (3, 9), (1, 2), padding=(1, 4)) + ), + weight_norm( + nn.Conv2d(channels, channels, (3, 3), (1, 1), padding=(1, 1)) + ), + ] + ) + self.band_convs = nn.ModuleList([convs() for _ in range(len(self.bands))]) + + self.conv_post = weight_norm( + nn.Conv2d(channels, 1, (3, 3), (1, 1), padding=(1, 1)) + ) + + def spectrogram(self, x: torch.Tensor) -> List[torch.Tensor]: + # Remove DC offset + x = x - x.mean(dim=-1, keepdims=True) + # Peak normalize the volume of input audio + x = 0.8 * x / (x.abs().max(dim=-1, keepdim=True)[0] + 1e-9) + x = self.spec_fn(x) + x = torch.view_as_real(x) + x = x.permute(0, 3, 2, 1) # [B, F, T, C] -> [B, C, T, F] + # Split into bands + x_bands = [x[..., b[0] : b[1]] for b in self.bands] + return x_bands + + def forward(self, x: torch.Tensor) -> Tuple[torch.Tensor, List[torch.Tensor]]: + x_bands = self.spectrogram(x.squeeze(1)) + fmap = [] + x = [] + + for band, stack in zip(x_bands, self.band_convs): + for i, layer in enumerate(stack): + band = layer(band) + band = torch.nn.functional.leaky_relu(band, 0.1) + if i > 0: + fmap.append(band) + x.append(band) + + x = torch.cat(x, dim=-1) + x = self.conv_post(x) + fmap.append(x) + + return x, fmap + + +# Method based on descript-audio-codec: https://github.com/descriptinc/descript-audio-codec +# Modified code adapted from https://github.com/gemelo-ai/vocos under the MIT license. +# LICENSE is in incl_licenses directory. +class MultiBandDiscriminator(nn.Module): + def __init__( + self, + h, + ): + """ + Multi-band multi-scale STFT discriminator, with the architecture based on https://github.com/descriptinc/descript-audio-codec. + and the modified code adapted from https://github.com/gemelo-ai/vocos. + """ + super().__init__() + # fft_sizes (list[int]): Tuple of window lengths for FFT. Defaults to [2048, 1024, 512] if not set in h. + self.fft_sizes = h.get("mbd_fft_sizes", [2048, 1024, 512]) + self.discriminators = nn.ModuleList( + [DiscriminatorB(window_length=w) for w in self.fft_sizes] + ) + + def forward(self, y: torch.Tensor, y_hat: torch.Tensor) -> Tuple[ + List[torch.Tensor], + List[torch.Tensor], + List[List[torch.Tensor]], + List[List[torch.Tensor]], + ]: + + y_d_rs = [] + y_d_gs = [] + fmap_rs = [] + fmap_gs = [] + + for d in self.discriminators: + y_d_r, fmap_r = d(x=y) + y_d_g, fmap_g = d(x=y_hat) + y_d_rs.append(y_d_r) + fmap_rs.append(fmap_r) + y_d_gs.append(y_d_g) + fmap_gs.append(fmap_g) + + return y_d_rs, y_d_gs, fmap_rs, fmap_gs + + +# Adapted from https://github.com/open-mmlab/Amphion/blob/main/models/vocoders/gan/discriminator/mssbcqtd.py under the MIT license. +# LICENSE is in incl_licenses directory. +class DiscriminatorCQT(nn.Module): + def __init__(self, cfg: AttrDict, hop_length: int, n_octaves:int, bins_per_octave: int): + super().__init__() + self.cfg = cfg + + self.filters = cfg["cqtd_filters"] + self.max_filters = cfg["cqtd_max_filters"] + self.filters_scale = cfg["cqtd_filters_scale"] + self.kernel_size = (3, 9) + self.dilations = cfg["cqtd_dilations"] + self.stride = (1, 2) + + self.in_channels = cfg["cqtd_in_channels"] + self.out_channels = cfg["cqtd_out_channels"] + self.fs = cfg["sampling_rate"] + self.hop_length = hop_length + self.n_octaves = n_octaves + self.bins_per_octave = bins_per_octave + + # Lazy-load + from nnAudio import features + + self.cqt_transform = features.cqt.CQT2010v2( + sr=self.fs * 2, + hop_length=self.hop_length, + n_bins=self.bins_per_octave * self.n_octaves, + bins_per_octave=self.bins_per_octave, + output_format="Complex", + pad_mode="constant", + ) + + self.conv_pres = nn.ModuleList() + for _ in range(self.n_octaves): + self.conv_pres.append( + nn.Conv2d( + self.in_channels * 2, + self.in_channels * 2, + kernel_size=self.kernel_size, + padding=self.get_2d_padding(self.kernel_size), + ) + ) + + self.convs = nn.ModuleList() + + self.convs.append( + nn.Conv2d( + self.in_channels * 2, + self.filters, + kernel_size=self.kernel_size, + padding=self.get_2d_padding(self.kernel_size), + ) + ) + + in_chs = min(self.filters_scale * self.filters, self.max_filters) + for i, dilation in enumerate(self.dilations): + out_chs = min( + (self.filters_scale ** (i + 1)) * self.filters, self.max_filters + ) + self.convs.append( + weight_norm( + nn.Conv2d( + in_chs, + out_chs, + kernel_size=self.kernel_size, + stride=self.stride, + dilation=(dilation, 1), + padding=self.get_2d_padding(self.kernel_size, (dilation, 1)), + ) + ) + ) + in_chs = out_chs + out_chs = min( + (self.filters_scale ** (len(self.dilations) + 1)) * self.filters, + self.max_filters, + ) + self.convs.append( + weight_norm( + nn.Conv2d( + in_chs, + out_chs, + kernel_size=(self.kernel_size[0], self.kernel_size[0]), + padding=self.get_2d_padding( + (self.kernel_size[0], self.kernel_size[0]) + ), + ) + ) + ) + + self.conv_post = weight_norm( + nn.Conv2d( + out_chs, + self.out_channels, + kernel_size=(self.kernel_size[0], self.kernel_size[0]), + padding=self.get_2d_padding((self.kernel_size[0], self.kernel_size[0])), + ) + ) + + self.activation = torch.nn.LeakyReLU(negative_slope=0.1) + self.resample = Resample(orig_freq=self.fs, new_freq=self.fs * 2) + + self.cqtd_normalize_volume = self.cfg.get("cqtd_normalize_volume", False) + if self.cqtd_normalize_volume: + print( + f"[INFO] cqtd_normalize_volume set to True. Will apply DC offset removal & peak volume normalization in CQTD!" + ) + + def get_2d_padding( + self, + kernel_size: typing.Tuple[int, int], + dilation: typing.Tuple[int, int] = (1, 1), + ): + return ( + ((kernel_size[0] - 1) * dilation[0]) // 2, + ((kernel_size[1] - 1) * dilation[1]) // 2, + ) + + def forward(self, x: torch.tensor) -> Tuple[torch.Tensor, List[torch.Tensor]]: + fmap = [] + + if self.cqtd_normalize_volume: + # Remove DC offset + x = x - x.mean(dim=-1, keepdims=True) + # Peak normalize the volume of input audio + x = 0.8 * x / (x.abs().max(dim=-1, keepdim=True)[0] + 1e-9) + + x = self.resample(x) + + z = self.cqt_transform(x) + + z_amplitude = z[:, :, :, 0].unsqueeze(1) + z_phase = z[:, :, :, 1].unsqueeze(1) + + z = torch.cat([z_amplitude, z_phase], dim=1) + z = torch.permute(z, (0, 1, 3, 2)) # [B, C, W, T] -> [B, C, T, W] + + latent_z = [] + for i in range(self.n_octaves): + latent_z.append( + self.conv_pres[i]( + z[ + :, + :, + :, + i * self.bins_per_octave : (i + 1) * self.bins_per_octave, + ] + ) + ) + latent_z = torch.cat(latent_z, dim=-1) + + for i, l in enumerate(self.convs): + latent_z = l(latent_z) + + latent_z = self.activation(latent_z) + fmap.append(latent_z) + + latent_z = self.conv_post(latent_z) + + return latent_z, fmap + + +class MultiScaleSubbandCQTDiscriminator(nn.Module): + def __init__(self, cfg: AttrDict): + super().__init__() + + self.cfg = cfg + # Using get with defaults + self.cfg["cqtd_filters"] = self.cfg.get("cqtd_filters", 32) + self.cfg["cqtd_max_filters"] = self.cfg.get("cqtd_max_filters", 1024) + self.cfg["cqtd_filters_scale"] = self.cfg.get("cqtd_filters_scale", 1) + self.cfg["cqtd_dilations"] = self.cfg.get("cqtd_dilations", [1, 2, 4]) + self.cfg["cqtd_in_channels"] = self.cfg.get("cqtd_in_channels", 1) + self.cfg["cqtd_out_channels"] = self.cfg.get("cqtd_out_channels", 1) + # Multi-scale params to loop over + self.cfg["cqtd_hop_lengths"] = self.cfg.get("cqtd_hop_lengths", [512, 256, 256]) + self.cfg["cqtd_n_octaves"] = self.cfg.get("cqtd_n_octaves", [9, 9, 9]) + self.cfg["cqtd_bins_per_octaves"] = self.cfg.get( + "cqtd_bins_per_octaves", [24, 36, 48] + ) + + self.discriminators = nn.ModuleList( + [ + DiscriminatorCQT( + self.cfg, + hop_length=self.cfg["cqtd_hop_lengths"][i], + n_octaves=self.cfg["cqtd_n_octaves"][i], + bins_per_octave=self.cfg["cqtd_bins_per_octaves"][i], + ) + for i in range(len(self.cfg["cqtd_hop_lengths"])) + ] + ) + + def forward(self, y: torch.Tensor, y_hat: torch.Tensor) -> Tuple[ + List[torch.Tensor], + List[torch.Tensor], + List[List[torch.Tensor]], + List[List[torch.Tensor]], + ]: + + y_d_rs = [] + y_d_gs = [] + fmap_rs = [] + fmap_gs = [] + + for disc in self.discriminators: + y_d_r, fmap_r = disc(y) + y_d_g, fmap_g = disc(y_hat) + y_d_rs.append(y_d_r) + fmap_rs.append(fmap_r) + y_d_gs.append(y_d_g) + fmap_gs.append(fmap_g) + + return y_d_rs, y_d_gs, fmap_rs, fmap_gs + + +class CombinedDiscriminator(nn.Module): + """ + Wrapper of chaining multiple discrimiantor architectures. + Example: combine mbd and cqtd as a single class + """ + + def __init__(self, list_discriminator: List[nn.Module]): + super().__init__() + self.discrimiantor = nn.ModuleList(list_discriminator) + + def forward(self, y: torch.Tensor, y_hat: torch.Tensor) -> Tuple[ + List[torch.Tensor], + List[torch.Tensor], + List[List[torch.Tensor]], + List[List[torch.Tensor]], + ]: + + y_d_rs = [] + y_d_gs = [] + fmap_rs = [] + fmap_gs = [] + + for disc in self.discrimiantor: + y_d_r, y_d_g, fmap_r, fmap_g = disc(y, y_hat) + y_d_rs.extend(y_d_r) + fmap_rs.extend(fmap_r) + y_d_gs.extend(y_d_g) + fmap_gs.extend(fmap_g) + + return y_d_rs, y_d_gs, fmap_rs, fmap_gs diff --git a/src/third_party/BigVGAN/env.py b/src/third_party/BigVGAN/env.py new file mode 100644 index 0000000000000000000000000000000000000000..ebc6c9a6b460f13b59761bebf69c43bd6a6ecf1d --- /dev/null +++ b/src/third_party/BigVGAN/env.py @@ -0,0 +1,18 @@ +# Adapted from https://github.com/jik876/hifi-gan under the MIT license. +# LICENSE is in incl_licenses directory. + +import os +import shutil + + +class AttrDict(dict): + def __init__(self, *args, **kwargs): + super(AttrDict, self).__init__(*args, **kwargs) + self.__dict__ = self + + +def build_env(config, config_name, path): + t_path = os.path.join(path, config_name) + if config != t_path: + os.makedirs(path, exist_ok=True) + shutil.copyfile(config, os.path.join(path, config_name)) diff --git a/src/third_party/BigVGAN/filelists/LibriTTS/dev-clean.txt b/src/third_party/BigVGAN/filelists/LibriTTS/dev-clean.txt new file mode 100644 index 0000000000000000000000000000000000000000..0566702740c44e766bdfa6f39a15bdb6953d5bdc --- /dev/null +++ b/src/third_party/BigVGAN/filelists/LibriTTS/dev-clean.txt @@ -0,0 +1,115 @@ +dev-clean/1272/128104/1272_128104_000001_000000|A 'JOLLY' ART CRITIC +dev-clean/1272/141231/1272_141231_000007_000003|And when he attacked, it was always there to beat him aside. +dev-clean/1272/141231/1272_141231_000033_000002|If anything, he was pressing the attack. +dev-clean/1462/170138/1462_170138_000012_000002|Dear me, Mac, the girl couldn't possibly be better, you know." +dev-clean/1462/170142/1462_170142_000002_000005|Alexander did not sit down. +dev-clean/1462/170142/1462_170142_000029_000001|"I meant to, but somehow I couldn't. +dev-clean/1462/170142/1462_170142_000046_000004|The sight of you, Bartley, to see you living and happy and successful-can I never make you understand what that means to me?" She pressed his shoulders gently. +dev-clean/1462/170145/1462_170145_000012_000003|There is a letter for you there, in my desk drawer. +dev-clean/1462/170145/1462_170145_000033_000000|She felt the strength leap in the arms that held her so lightly. +dev-clean/1673/143397/1673_143397_000031_000007|He attempted to remove or intimidate the leaders by a common sentence, of acquittal or condemnation; he invested his representatives at Ephesus with ample power and military force; he summoned from either party eight chosen deputies to a free and candid conference in the neighborhood of the capital, far from the contagion of popular frenzy. +dev-clean/174/168635/174_168635_000040_000000|To teach Cosette to read, and to let her play, this constituted nearly the whole of Jean Valjean's existence. +dev-clean/174/50561/174_50561_000058_000001|They have the end of the game to themselves.) +dev-clean/174/84280/174_84280_000015_000000|And perhaps in this story I have said enough for you to understand why Mary has identified herself with something world-wide, has added to herself a symbolical value, and why it is I find in the whole crowded spectacle of mankind, a quality that is also hers, a sense of fine things entangled and stifled and unable to free themselves from the ancient limiting jealousies which law and custom embody. +dev-clean/1919/142785/1919_142785_000063_000000|[Illustration: SHALOT.] +dev-clean/1919/142785/1919_142785_000131_000001|Cut the bread into thin slices, place them in a cool oven overnight, and when thoroughly dry and crisp, roll them down into fine crumbs. +dev-clean/1988/147956/1988_147956_000016_000009|He was neatly dressed. +dev-clean/1988/148538/1988_148538_000015_000007|These persons then displayed towards each other precisely the same puerile jealousies which animate the men of democracies, the same eagerness to snatch the smallest advantages which their equals contested, and the same desire to parade ostentatiously those of which they were in possession. +dev-clean/1988/24833/1988_24833_000028_000003|He's taking the kid for a walk when a thunderstorm blows up. +dev-clean/1988/24833/1988_24833_000059_000000|"Doesn't pay enough?" Pop asks. +dev-clean/1993/147149/1993_147149_000051_000002|So leaving kind messages to George and Jane Wilson, and hesitating whether she might dare to send a few kind words to Jem, and deciding that she had better not, she stepped out into the bright morning light, so fresh a contrast to the darkened room where death had been. +dev-clean/1993/147965/1993_147965_000003_000004|I suppose, in the crowded clutter of their cave, the old man had come to believe that peace and order had vanished from the earth, or existed only in the old world he had left so far behind. +dev-clean/1993/147966/1993_147966_000020_000003|We found the chickens asleep; perhaps they thought night had come to stay. +dev-clean/2035/147960/2035_147960_000019_000001|He is all over Jimmy's boots. I scream for him to run, but he just hit and hit that snake like he was crazy." +dev-clean/2035/147961/2035_147961_000011_000002|He grew more and more excited, and kept pointing all around his bed, as if there were things there and he wanted mr Shimerda to see them. +dev-clean/2035/147961/2035_147961_000025_000002|Beside a frozen pond something happened to the other sledge; peter saw it plainly. +dev-clean/2035/152373/2035_152373_000010_000007|saint Aidan, the Apostle of Northumbria, had refused the late Egfrid's father absolution, on one occasion, until he solemnly promised to restore their freedom to certain captives of this description. +dev-clean/2086/149214/2086_149214_000005_000002|It is a legend prolonging itself, from an epoch now gray in the distance, down into our own broad daylight, and bringing along with it some of its legendary mist, which the reader, according to his pleasure, may either disregard, or allow it to float almost imperceptibly about the characters and events for the sake of a picturesque effect. +dev-clean/2086/149220/2086_149220_000016_000003|In short, I make pictures out of sunshine; and, not to be too much dazzled with my own trade, I have prevailed with Miss Hepzibah to let me lodge in one of these dusky gables. +dev-clean/2086/149220/2086_149220_000028_000000|Phoebe was on the point of retreating, but turned back, with some hesitation; for she did not exactly comprehend his manner, although, on better observation, its feature seemed rather to be lack of ceremony than any approach to offensive rudeness. +dev-clean/2277/149874/2277_149874_000007_000001|Her husband asked a few questions and sat down to read the evening paper. +dev-clean/2277/149896/2277_149896_000007_000006|He saw only her pretty face and neat figure and wondered why life was not arranged so that such joy as he found with her could be steadily maintained. +dev-clean/2277/149896/2277_149896_000025_000008|He jangled it fiercely several times in succession, but without avail. +dev-clean/2277/149897/2277_149897_000023_000000|"Well?" said Hurstwood. +dev-clean/2277/149897/2277_149897_000046_000002|He troubled over many little details and talked perfunctorily to everybody. +dev-clean/2412/153954/2412_153954_000004_000005|Even in middle age they were still comely, and the old grey haired women at their cottage doors had a dignity, not to say majesty, of their own. +dev-clean/2428/83699/2428_83699_000009_000000|Now it is a remarkable thing that I have always had an extraordinary predilection for the name Madge. +dev-clean/2428/83699/2428_83699_000024_000004|I had long been wishing that an old-fashioned Christmas had been completely extinct before I had thought of adventuring in quest of one. +dev-clean/2428/83699/2428_83699_000047_000000|"Perhaps you had better come inside." +dev-clean/2428/83705/2428_83705_000015_000004|I did not want any unpleasantness; and I am quite sure there would have been unpleasantness had I demurred. +dev-clean/2428/83705/2428_83705_000034_000002|"And what," inquired mrs Macpherson, "has Mary Ann given you?" +dev-clean/251/118436/251_118436_000017_000001|This man was clad in a brown camel hair robe and sandals, and a green turban was on his head. His expression was tranquil, his gaze impersonal. +dev-clean/251/136532/251_136532_000000_000003|Fitzgerald was still trying to find out how the germ had been transmitted. +dev-clean/251/136532/251_136532_000020_000004|Without question, he had become, overnight, the most widely known archaeologist in history. +dev-clean/251/137823/251_137823_000025_000001|Or grazed, at least," Tom added thankfully. +dev-clean/251/137823/251_137823_000054_000002|The two girls were as much upset as Tom's mother. +dev-clean/2803/154320/2803_154320_000017_000004|Think of Lady Glenarvan; think of Mary Grant!" +dev-clean/2803/154328/2803_154328_000028_000000|Wilson and Olbinett joined their companions, and all united to dig through the wall-john with his dagger, the others with stones taken from the ground, or with their nails, while Mulrady, stretched along the ground, watched the native guard through a crevice of the matting. +dev-clean/2803/154328/2803_154328_000080_000003|Where chance led them, but at any rate they were free. +dev-clean/2803/161169/2803_161169_000011_000019|What do you think of that from the coal tar. +dev-clean/2902/9008/2902_9008_000009_000001|He was a Greek, also, but of a more common, and, perhaps, lower type; dark and fiery, thin and graceful; his delicate figure and cheeks, wasted by meditation, harmonised well with the staid and simple philosophic cloak which he wore as a sign of his profession. +dev-clean/2902/9008/2902_9008_000048_000003|For aught I know or care, the plot may be an exactly opposite one, and the Christians intend to murder all the Jews. +dev-clean/3000/15664/3000_15664_000013_000004|These volcanic caves are not wanting in interest, and it is well to light a pitch pine torch and take a walk in these dark ways of the underworld whenever opportunity offers, if for no other reason to see with new appreciation on returning to the sunshine the beauties that lie so thick about us. +dev-clean/3000/15664/3000_15664_000029_000002|Thus the Shasta River issues from a large lake like spring in Shasta Valley, and about two thirds of the volume of the McCloud gushes forth in a grand spring on the east side of the mountain, a few miles back from its immediate base. +dev-clean/3170/137482/3170_137482_000010_000004|The nobility, the merchants, even workmen in good circumstances, are never seen in the 'magazzino', for cleanliness is not exactly worshipped in such places. +dev-clean/3170/137482/3170_137482_000037_000001|He was celebrated in Venice not only for his eloquence and his great talents as a statesman, but also for the gallantries of his youth. +dev-clean/3536/23268/3536_23268_000028_000000|"It is not the first time, I believe, you have acted contrary to that, Miss Milner," replied mrs Horton, and affected a tenderness of voice, to soften the harshness of her words. +dev-clean/3576/138058/3576_138058_000019_000003|He wondered to see the lance leaning against the tree, the shield on the ground, and Don Quixote in armour and dejected, with the saddest and most melancholy face that sadness itself could produce; and going up to him he said, "Be not so cast down, good man, for you have not fallen into the hands of any inhuman Busiris, but into Roque Guinart's, which are more merciful than cruel." +dev-clean/3752/4943/3752_4943_000026_000002|Lie quiet!" +dev-clean/3752/4943/3752_4943_000056_000002|His flogging wouldn't have killed a flea." +dev-clean/3752/4944/3752_4944_000031_000000|"Well now!" said Meekin, with asperity, "I don't agree with you. Everybody seems to be against that poor fellow-Captain Frere tried to make me think that his letters contained a hidden meaning, but I don't believe they did. +dev-clean/3752/4944/3752_4944_000063_000003|He'd rather kill himself." +dev-clean/3752/4944/3752_4944_000094_000000|"The Government may go to----, and you, too!" roared Burgess. +dev-clean/3853/163249/3853_163249_000058_000000|"I've done it, mother: tell me you're not sorry." +dev-clean/3853/163249/3853_163249_000125_000004|Help me to be brave and strong, David: don't let me complain or regret, but show me what lies beyond, and teach me to believe that simply doing the right is reward and happiness enough." +dev-clean/5338/24615/5338_24615_000004_000003|It had been built at a period when castles were no longer necessary, and when the Scottish architects had not yet acquired the art of designing a domestic residence. +dev-clean/5338/284437/5338_284437_000031_000001|A powerful ruler ought to be rich and to live in a splendid palace. +dev-clean/5536/43358/5536_43358_000012_000001|Being a natural man, the Indian was intensely poetical. +dev-clean/5536/43359/5536_43359_000015_000000|The family was not only the social unit, but also the unit of government. +dev-clean/5694/64025/5694_64025_000004_000006|Our regiment was the advance guard on Saturday evening, and did a little skirmishing; but General Gladden's brigade passed us and assumed a position in our immediate front. +dev-clean/5694/64029/5694_64029_000006_000005|I read it, and looked up to hand it back to him, when I discovered that he had a pistol cocked and leveled in my face, and says he, "Drop that gun; you are my prisoner." I saw there was no use in fooling about it. +dev-clean/5694/64029/5694_64029_000024_000002|The ground was literally covered with blue coats dead; and, if I remember correctly, there were eighty dead horses. +dev-clean/5694/64038/5694_64038_000015_000002|I could not imagine what had become of him. +dev-clean/5895/34615/5895_34615_000013_000003|Man can do nothing to create beauty, but everything to produce ugliness. +dev-clean/5895/34615/5895_34615_000025_000000|With this exception, Gwynplaine's laugh was everlasting. +dev-clean/5895/34622/5895_34622_000029_000002|In the opposite corner was the kitchen. +dev-clean/5895/34629/5895_34629_000021_000005|The sea is a wall; and if Voltaire-a thing which he very much regretted when it was too late-had not thrown a bridge over to Shakespeare, Shakespeare might still be in England, on the other side of the wall, a captive in insular glory. +dev-clean/6241/61943/6241_61943_000020_000000|My uncle came out of his cabin pale, haggard, thin, but full of enthusiasm, his eyes dilated with pleasure and satisfaction. +dev-clean/6241/61946/6241_61946_000014_000000|The rugged summits of the rocky hills were dimly visible on the edge of the horizon, through the misty fogs; every now and then some heavy flakes of snow showed conspicuous in the morning light, while certain lofty and pointed rocks were first lost in the grey low clouds, their summits clearly visible above, like jagged reefs rising from a troublous sea. +dev-clean/6241/61946/6241_61946_000051_000001|Then my uncle, myself, and guide, two boatmen and the four horses got into a very awkward flat bottom boat. +dev-clean/6295/64301/6295_64301_000010_000002|The music was broken, and Joseph left alone with the dumb instruments. +dev-clean/6313/66125/6313_66125_000020_000002|"Are you hurt?" +dev-clean/6313/66125/6313_66125_000053_000000|"Are you ready?" +dev-clean/6313/66129/6313_66129_000011_000001|"Cold water is the most nourishing thing we've touched since last night." +dev-clean/6313/66129/6313_66129_000045_000004|Of course, dogs can't follow the trail of an animal as well, now, as they could with snow on the ground. +dev-clean/6313/66129/6313_66129_000081_000000|Stacy dismounted and removed the hat carefully to one side. +dev-clean/6313/76958/6313_76958_000029_000000|Instantly there was a chorus of yells and snarls from the disturbed cowpunchers, accompanied by dire threats as to what they would do to the gopher did he ever disturb their rest in that way again. +dev-clean/6313/76958/6313_76958_000073_000001|"Those fellows have to go out. +dev-clean/6319/275224/6319_275224_000014_000001|And what is the matter with the beautiful straggling branches, that they are to be cut off as fast as they appear? +dev-clean/6319/57405/6319_57405_000019_000000|"It is rather a silly thing to do," said Deucalion; "and yet there can be no harm in it, and we shall see what will happen." +dev-clean/6319/64726/6319_64726_000017_000002|Then the prince took the princess by the hand; she was dressed in great splendour, but he did not hint that she looked as he had seen pictures of his great grandmother look; he thought her all the more charming for that. +dev-clean/6345/93302/6345_93302_000000_000001|All LibriVox recordings are in the public domain. +dev-clean/6345/93302/6345_93302_000049_000000|The fine tact of a noble woman seemed to have deserted her. +dev-clean/6345/93302/6345_93302_000073_000000|So she said- +dev-clean/6345/93306/6345_93306_000024_000002|What is it? +dev-clean/652/130737/652_130737_000031_000001|Good aroma. +dev-clean/7850/111771/7850_111771_000009_000001|After various flanking movements and costly assaults, the problem of taking Lee narrowed itself down to a siege of Petersburg. +dev-clean/7850/281318/7850_281318_000012_000000|She began to show them how to weave the bits of things together into nests, as they should be made. +dev-clean/7850/286674/7850_286674_000006_000001|You would think that, with six legs apiece and three joints in each leg, they might walk quite fast, yet they never did. +dev-clean/7850/73752/7850_73752_000006_000003|What a Neapolitan ball was his career then! +dev-clean/7976/105575/7976_105575_000009_000000|The burying party the next morning found nineteen dead Rebels lying together at one place. +dev-clean/7976/105575/7976_105575_000017_000000|Our regiment now pursued the flying Rebels with great vigor. +dev-clean/7976/110124/7976_110124_000021_000001|"We two are older and wiser than you are. It is for us to determine what shall be done. +dev-clean/7976/110124/7976_110124_000053_000002|The doors were strong and held securely. +dev-clean/7976/110523/7976_110523_000027_000000|"We will go in here," said Hansel, "and have a glorious feast. +dev-clean/8297/275154/8297_275154_000008_000000|Was this man-haggard, pallid, shabby, looking at him piteously with bloodshot eyes-the handsome, pleasant, prosperous brother whom he remembered? +dev-clean/8297/275154/8297_275154_000024_000011|Tell me where my wife is living now?" +dev-clean/8297/275155/8297_275155_000013_000006|What a perfect gentleman!" +dev-clean/8297/275155/8297_275155_000037_000000|"Say thoroughly worthy of the course forced upon me and my daughter by your brother's infamous conduct-and you will be nearer the mark!" +dev-clean/8297/275156/8297_275156_000013_000005|No more of it now. +dev-clean/84/121123/84_121123_000009_000000|But in less than five minutes the staircase groaned beneath an extraordinary weight. +dev-clean/84/121123/84_121123_000054_000000|It was something terrible to witness the silent agony, the mute despair of Noirtier, whose tears silently rolled down his cheeks. +dev-clean/84/121550/84_121550_000064_000000|And lo! a sudden lustre ran across On every side athwart the spacious forest, Such that it made me doubt if it were lightning. +dev-clean/84/121550/84_121550_000156_000000|Nor prayer for inspiration me availed, By means of which in dreams and otherwise I called him back, so little did he heed them. +dev-clean/84/121550/84_121550_000247_000000|Thus Beatrice; and I, who at the feet Of her commandments all devoted was, My mind and eyes directed where she willed. +dev-clean/8842/302203/8842_302203_000001_000001|And I remember that on the ninth day, being overcome with intolerable pain, a thought came into my mind concerning my lady: but when it had a little nourished this thought, my mind returned to its brooding over mine enfeebled body. diff --git a/src/third_party/BigVGAN/filelists/LibriTTS/dev-other.txt b/src/third_party/BigVGAN/filelists/LibriTTS/dev-other.txt new file mode 100644 index 0000000000000000000000000000000000000000..af85b4fe40a33f04445abf34576457bf7ad150a0 --- /dev/null +++ b/src/third_party/BigVGAN/filelists/LibriTTS/dev-other.txt @@ -0,0 +1,93 @@ +dev-other/116/288045/116_288045_000003_000000|PART one +dev-other/116/288045/116_288045_000034_000001|He was only an idol. +dev-other/116/288047/116_288047_000002_000002|Observing the sun, the moon, and the stars overhead, the primitive man wished to account for them. +dev-other/116/288048/116_288048_000001_000000|Let me now give an idea of the method I propose to follow in the study of this subject. +dev-other/116/288048/116_288048_000020_000003|Leaving out Judas, and counting Matthias, who was elected in his place, we have thirteen apostles. +dev-other/1255/138279/1255_138279_000012_000000|"One. +dev-other/1255/138279/1255_138279_000049_000001|Will it be by banns or license?" +dev-other/1255/74899/1255_74899_000020_000000|"Pardon me. +dev-other/1255/90407/1255_90407_000006_000001|But, as the rain gave not the least sign of cessation, he observed: 'I think we shall have to go back.' +dev-other/1255/90407/1255_90407_000039_000002|Into it they plodded without pause, crossing the harbour bridge about midnight, wet to the skin. +dev-other/1255/90413/1255_90413_000023_000001|'Now what the devil this means I cannot tell,' he said to himself, reflecting stock still for a moment on the stairs. +dev-other/1585/131718/1585_131718_000025_000009|Edison marginalized documents extensively. +dev-other/1630/141772/1630_141772_000000_000002|Suddenly he again felt that he was alive and suffering from a burning, lacerating pain in his head. +dev-other/1630/141772/1630_141772_000039_000000|The quiet home life and peaceful happiness of Bald Hills presented itself to him. +dev-other/1630/73710/1630_73710_000019_000003|I almost wish papa would return, though I dread to see him. +dev-other/1630/96099/1630_96099_000033_000001|Why did you not follow him? +dev-other/1650/157641/1650_157641_000037_000001|mr w m +dev-other/1650/173551/1650_173551_000025_000000|Pierre went into that gloomy study which he had entered with such trepidation in his benefactor's lifetime. +dev-other/1651/136854/1651_136854_000046_000005|I have, however, this of gratitude, that I think of you with regard, when I do not, perhaps, give the proofs which I ought, of being, Sir, +dev-other/1686/142278/1686_142278_000015_000000|'No! not doubts as to religion; not the slightest injury to that.' He paused. +dev-other/1686/142278/1686_142278_000042_000001|Margaret was nearly upset again into a burst of crying. +dev-other/1701/141759/1701_141759_000001_000001|Not till midwinter was the count at last handed a letter addressed in his son's handwriting. +dev-other/1701/141759/1701_141759_000048_000000|"Why should you be ashamed?" +dev-other/1701/141760/1701_141760_000013_000003|"I only sent you the note yesterday by Bolkonski-an adjutant of Kutuzov's, who's a friend of mine. +dev-other/1701/141760/1701_141760_000056_000000|In spite of Prince Andrew's disagreeable, ironical tone, in spite of the contempt with which Rostov, from his fighting army point of view, regarded all these little adjutants on the staff of whom the newcomer was evidently one, Rostov felt confused, blushed, and became silent. +dev-other/2506/13150/2506_13150_000022_000000|--Nay-if you don't believe me, you may read the chapter for your pains. +dev-other/3660/172182/3660_172182_000012_000007|And a year, and a second, and a third, he proceeded thus, until his fame had flown over the face of the kingdom. +dev-other/3660/172183/3660_172183_000011_000000|So the maiden went forward, keeping in advance of Geraint, as he had desired her; and it grieved him as much as his wrath would permit, to see a maiden so illustrious as she having so much trouble with the care of the horses. +dev-other/3660/172183/3660_172183_000019_000040|Come with me to the court of a son in law of my sister, which is near here, and thou shalt have the best medical assistance in the kingdom." +dev-other/3660/6517/3660_6517_000036_000002|Bright sunshine. +dev-other/3660/6517/3660_6517_000059_000005|Not a single one has lost his good spirits. +dev-other/3663/172005/3663_172005_000022_000000|She must cross the Slide Brook valley, if possible, and gain the mountain opposite. +dev-other/3663/172528/3663_172528_000016_000008|He had been brought by my very dear friend Luca Martini, who passed the larger portion of the day with me. +dev-other/3915/57461/3915_57461_000018_000001|In a fit of madness I was tempted to kill and rob you. +dev-other/3915/98647/3915_98647_000018_000006|Thus the old custom is passing away. +dev-other/4323/13259/4323_13259_000009_000011|What would Jesus do? +dev-other/4323/13259/4323_13259_000020_000003|It seems she had been recently converted during the evangelist's meetings, and was killed while returning from one of the meetings in company with other converts and some of her friends. +dev-other/4323/18416/4323_18416_000019_000001|So she was asked to sing at musicales and receptions without end, until Alexia exclaimed at last, "They are all raving, stark mad over her, and it's all Polly's own fault, the whole of it." +dev-other/4323/18416/4323_18416_000050_000000|"I know, child; you think your old Grandpapa does just about right," said mr King soothingly, and highly gratified. +dev-other/4323/18416/4323_18416_000079_000002|"And I can't tolerate any thoughts I cannot speak." +dev-other/4323/55228/4323_55228_000028_000000|"Pete told you that I didn't care for any girl, only to paint?" demanded Bertram, angry and mystified. +dev-other/4323/55228/4323_55228_000071_000000|There was another silence. +dev-other/4570/102353/4570_102353_000001_000000|CHAPTER four. +dev-other/4570/14911/4570_14911_000009_000002|EYES-Brown, dark hazel or hazel, not deep set nor bulgy, and with a mild expression. +dev-other/4570/56594/4570_56594_000012_000000|"'No,' says the gentleman. +dev-other/4831/18525/4831_18525_000028_000000|"Oh! isn't it 'Oats, Peas, Beans, and Barley grow'?" cried Polly, as they watched them intently. +dev-other/4831/18525/4831_18525_000078_000001|"I want to write, too, I do," she cried, very much excited. +dev-other/4831/18525/4831_18525_000122_000000|"O dear me!" exclaimed Polly, softly, for she couldn't even yet get over that dreadful beginning. +dev-other/4831/25894/4831_25894_000022_000003|The other days were very much like this; sometimes they made more, sometimes less, but Tommo always 'went halves;' and Tessa kept on, in spite of cold and weariness, for her plans grew as her earnings increased, and now she hoped to get useful things, instead of candy and toys alone. +dev-other/4831/29134/4831_29134_000001_000000|The session was drawing toward its close. +dev-other/4831/29134/4831_29134_000018_000000|"So this poor little boy grew up to be a man, and had to go out in the world, far from home and friends to earn his living. +dev-other/5543/27761/5543_27761_000019_000000|Her mother went to hide. +dev-other/5543/27761/5543_27761_000065_000000|"Agathya says so, madam," answered Fedosya; "it's she that knows." +dev-other/5543/27761/5543_27761_000107_000000|"Sima, my dear, don't agitate yourself," said Sergey Modestovich in a whisper. +dev-other/5849/50873/5849_50873_000026_000000|"He has promised to do so." +dev-other/5849/50873/5849_50873_000074_000000|"The boy did it! +dev-other/5849/50962/5849_50962_000010_000000|"It's a schooner," said mr Bingham to mr Minturn, "and she has a very heavy cargo." +dev-other/5849/50963/5849_50963_000009_000003|Well, it was a long, slow job to drag those heavy logs around that point, and just when we were making headway, along comes a storm that drove the schooner and canoes out of business." +dev-other/5849/50964/5849_50964_000018_000001|There were the shells to be looked after, the fish nets, besides Downy, the duck, and Snoop, the cat. +dev-other/6123/59150/6123_59150_000016_000001|He kicked him two or three times with his heel in the face. +dev-other/6123/59186/6123_59186_000008_000000|"Catering care" is an appalling phrase. +dev-other/6267/53049/6267_53049_000007_000001|"I'd better be putting my grey matter into that algebra instead of wasting it plotting for a party dress that I certainly can't get. +dev-other/6267/53049/6267_53049_000045_000001|I am named after her." +dev-other/6267/65525/6267_65525_000018_000000|Dear mr Lincoln: +dev-other/6267/65525/6267_65525_000045_000006|You can't mistake it." +dev-other/6455/66379/6455_66379_000020_000002|(Deal, sir, if you please; better luck next time.)" +dev-other/6455/67803/6455_67803_000038_000000|"Yes," he answered. +dev-other/6467/56885/6467_56885_000012_000001|As you are so generously taking her on trust, may she never cause you a moment's regret. +dev-other/6467/97061/6467_97061_000010_000000|A terrible battle ensued, in which both kings performed prodigies of valour. +dev-other/6841/88291/6841_88291_000006_000006|One stood waiting for them to finish, a sheaf of long j h stamping irons in his hand. +dev-other/6841/88291/6841_88291_000019_000006|Cries arose in a confusion: "Marker" "Hot iron!" "Tally one!" Dust eddied and dissipated. +dev-other/6841/88294/6841_88294_000010_000003|Usually I didn't bother with his talk, for it didn't mean anything, but something in his voice made me turn. +dev-other/6841/88294/6841_88294_000048_000000|He stood there looking straight at me without winking or offering to move. +dev-other/700/122866/700_122866_000006_000003|You've been thirteen for a month, so I suppose it doesn't seem such a novelty to you as it does to me. +dev-other/700/122866/700_122866_000023_000006|Ruby Gillis is rather sentimental. +dev-other/700/122867/700_122867_000012_000004|My career is closed. +dev-other/700/122867/700_122867_000033_000003|At the end of the week Marilla said decidedly: +dev-other/700/122868/700_122868_000015_000003|mrs Lynde says that all play acting is abominably wicked." +dev-other/700/122868/700_122868_000038_000001|And Ruby is in hysterics-oh, Anne, how did you escape?" +dev-other/7601/101622/7601_101622_000018_000002|The very girls themselves set them on: +dev-other/7601/175351/7601_175351_000031_000008|Still, during the nights which followed the fifteenth of August, darkness was never profound; although the sun set, he still gave sufficient light by refraction. +dev-other/7641/96252/7641_96252_000003_000006|For these are careful only for themselves, for their own egoism, just like the bandit, from whom they are only distinguished by the absurdity of their means. +dev-other/7641/96670/7641_96670_000013_000001|The mist lifted suddenly and she saw three strangers in the palace courtyard. +dev-other/7641/96684/7641_96684_000009_000000|"What years of happiness have been mine, O Apollo, through your friendship for me," said Admetus. +dev-other/7641/96684/7641_96684_000031_000002|How noble it was of Admetus to bring him into his house and give entertainment to him while such sorrow was upon him. +dev-other/7697/105815/7697_105815_000048_000002|And they brought out the jaw bone of an ass with which Samson did such great feats, and the sling and stone with which David slew Goliath of Gath. +dev-other/8173/294714/8173_294714_000006_000001|"Don't spoil my pleasure in seeing you again by speaking of what can never be! Have you still to be told how it is that you find me here alone with my child?" +dev-other/8173/294714/8173_294714_000027_000001|What was there to prevent her from insuring her life, if she pleased, and from so disposing of the insurance as to give Van Brandt a direct interest in her death? +dev-other/8254/115543/8254_115543_000034_000000|"Yes, and how he orders every one about him. +dev-other/8254/84205/8254_84205_000029_000000|"I'm not afraid of them hitting me, my lad," said Griggs confidently. "Being shot at by fellows with bows and arrows sounds bad enough, but there's not much risk here." +dev-other/8254/84205/8254_84205_000073_000000|"Right; I do, neighbour, and it's very handsome of you to offer me the chance to back out. +dev-other/8288/274162/8288_274162_000023_000000|"Exactly. +dev-other/8288/274162/8288_274162_000078_000000|"So much the worse. diff --git a/src/third_party/BigVGAN/filelists/LibriTTS/parse_libritts.py b/src/third_party/BigVGAN/filelists/LibriTTS/parse_libritts.py new file mode 100644 index 0000000000000000000000000000000000000000..ec5f64d298ab59b594b0ac70e53f26df74e9b4ad --- /dev/null +++ b/src/third_party/BigVGAN/filelists/LibriTTS/parse_libritts.py @@ -0,0 +1,77 @@ +# Copyright (c) 2024 NVIDIA CORPORATION. +# Licensed under the MIT license. + +import os, glob + + +def get_wav_and_text_filelist(data_root, data_type, subsample=1): + wav_list = sorted( + [ + path.replace(data_root, "")[1:] + for path in glob.glob(os.path.join(data_root, data_type, "**/**/*.wav")) + ] + ) + wav_list = wav_list[::subsample] + txt_filelist = [path.replace(".wav", ".normalized.txt") for path in wav_list] + + txt_list = [] + for txt_file in txt_filelist: + with open(os.path.join(data_root, txt_file), "r") as f_txt: + text = f_txt.readline().strip("\n") + txt_list.append(text) + wav_list = [path.replace(".wav", "") for path in wav_list] + + return wav_list, txt_list + + +def write_filelist(output_path, wav_list, txt_list): + with open(output_path, "w") as f: + for i in range(len(wav_list)): + filename = wav_list[i] + "|" + txt_list[i] + f.write(filename + "\n") + + +if __name__ == "__main__": + + data_root = "filelists/LibriTTS" + + # Dev and test sets. subsample each sets to get ~100 utterances + data_type_list = ["dev-clean", "dev-other", "test-clean", "test-other"] + subsample_list = [50, 50, 50, 50] + for data_type, subsample in zip(data_type_list, subsample_list): + print(f"processing {data_type}") + data_path = os.path.join(data_root, data_type) + assert os.path.exists(data_path), ( + f"path {data_path} not found. make sure the path is accessible by creating the symbolic link using the following command: " + f"ln -s /path/to/your/{data_path} {data_path}" + ) + wav_list, txt_list = get_wav_and_text_filelist(data_root, data_type, subsample) + write_filelist(os.path.join(data_root, data_type + ".txt"), wav_list, txt_list) + + # Training and seen speaker validation datasets (libritts-full): train-clean-100 + train-clean-360 + train-other-500 + wav_list_train, txt_list_train = [], [] + for data_type in ["train-clean-100", "train-clean-360", "train-other-500"]: + print(f"processing {data_type}") + data_path = os.path.join(data_root, data_type) + assert os.path.exists(data_path), ( + f"path {data_path} not found. make sure the path is accessible by creating the symbolic link using the following command: " + f"ln -s /path/to/your/{data_path} {data_path}" + ) + wav_list, txt_list = get_wav_and_text_filelist(data_root, data_type) + wav_list_train.extend(wav_list) + txt_list_train.extend(txt_list) + + # Split the training set so that the seen speaker validation set contains ~100 utterances + subsample_val = 3000 + wav_list_val, txt_list_val = ( + wav_list_train[::subsample_val], + txt_list_train[::subsample_val], + ) + del wav_list_train[::subsample_val] + del txt_list_train[::subsample_val] + write_filelist( + os.path.join(data_root, "train-full.txt"), wav_list_train, txt_list_train + ) + write_filelist(os.path.join(data_root, "val-full.txt"), wav_list_val, txt_list_val) + + print("done") diff --git a/src/third_party/BigVGAN/filelists/LibriTTS/test-clean.txt b/src/third_party/BigVGAN/filelists/LibriTTS/test-clean.txt new file mode 100644 index 0000000000000000000000000000000000000000..13effbf405736ce8b43e07f11b87445d1b5ffaa3 --- /dev/null +++ b/src/third_party/BigVGAN/filelists/LibriTTS/test-clean.txt @@ -0,0 +1,97 @@ +test-clean/1089/134686/1089_134686_000001_000001|He hoped there would be stew for dinner, turnips and carrots and bruised potatoes and fat mutton pieces to be ladled out in thick peppered flour fattened sauce. Stuff it into you, his belly counselled him. +test-clean/1089/134686/1089_134686_000020_000001|We can scut the whole hour. +test-clean/1089/134691/1089_134691_000004_000001|Yet her mistrust pricked him more keenly than his father's pride and he thought coldly how he had watched the faith which was fading down in his soul ageing and strengthening in her eyes. +test-clean/1089/134691/1089_134691_000027_000004|Now, at the name of the fabulous artificer, he seemed to hear the noise of dim waves and to see a winged form flying above the waves and slowly climbing the air. +test-clean/1188/133604/1188_133604_000018_000002|There are just four touches-fine as the finest penmanship-to do that beak; and yet you will find that in the peculiar paroquettish mumbling and nibbling action of it, and all the character in which this nibbling beak differs from the tearing beak of the eagle, it is impossible to go farther or be more precise. +test-clean/121/121726/121_121726_000046_000003|Tied to a woman. +test-clean/121/127105/121_127105_000024_000000|He laughed for the first time. +test-clean/1284/1180/1284_1180_000001_000000|The Crooked Magician +test-clean/1284/1181/1284_1181_000005_000000|The head of the Patchwork Girl was the most curious part of her. +test-clean/1320/122612/1320_122612_000019_000005|It is true that the horses are here, but the Hurons are gone; let us, then, hunt for the path by which they parted." +test-clean/1320/122612/1320_122612_000056_000002|Then he reappeared, creeping along the earth, from which his dress was hardly distinguishable, directly in the rear of his intended captive. +test-clean/1580/141083/1580_141083_000012_000000|"The first page on the floor, the second in the window, the third where you left it," said he. +test-clean/1580/141083/1580_141083_000041_000003|Above were three students, one on each story. +test-clean/1580/141083/1580_141083_000063_000001|Holmes held it out on his open palm in the glare of the electric light. +test-clean/1580/141083/1580_141083_000110_000001|Where were you when you began to feel bad?" +test-clean/1580/141084/1580_141084_000024_000002|Pencils, too, and knives-all was satisfactory. +test-clean/1580/141084/1580_141084_000060_000001|"I frankly admit that I am unable to prove it. +test-clean/1580/141084/1580_141084_000085_000000|"Good heavens! have you nothing to add?" cried Soames. +test-clean/1995/1826/1995_1826_000022_000001|Miss Taylor did not know much about cotton, but at least one more remark seemed called for. +test-clean/1995/1836/1995_1836_000016_000001|No, of course there was no immediate danger; but when people were suddenly thrust beyond their natural station, filled with wild ideas and impossible ambitions, it meant terrible danger to Southern white women. +test-clean/1995/1837/1995_1837_000024_000000|He heard that she was down stairs and ran to meet her with beating heart. +test-clean/2300/131720/2300_131720_000016_000005|Having travelled around the world, I had cultivated an indifference to any special difficulties of that kind. +test-clean/2300/131720/2300_131720_000030_000005|I telephoned again, and felt something would happen, but fortunately it did not. +test-clean/237/126133/237_126133_000002_000004|It got to be noticed finally; and one and all redoubled their exertions to make everything twice as pleasant as ever! +test-clean/237/126133/237_126133_000049_000000|But the chubby face didn't look up brightly, as usual: and the next moment, without a bit of warning, Phronsie sprang past them all, even Polly, and flung herself into mr King's arms, in a perfect torrent of sobs. +test-clean/237/134493/237_134493_000008_000003|Alexandra lets you sleep late. +test-clean/237/134500/237_134500_000001_000001|Frank sat up until a late hour reading the Sunday newspapers. +test-clean/237/134500/237_134500_000014_000000|"I don't know all of them, but I know lindens are. +test-clean/237/134500/237_134500_000034_000000|She sighed despondently. +test-clean/260/123286/260_123286_000019_000002|Therefore don't talk to me about views and prospects." +test-clean/260/123286/260_123286_000049_000005|He shakes his head negatively. +test-clean/260/123288/260_123288_000016_000002|It rushes on from the farthest recesses of the vast cavern. +test-clean/260/123288/260_123288_000043_000001|I could just see my uncle at full length on the raft, and Hans still at his helm and spitting fire under the action of the electricity which has saturated him. +test-clean/2830/3979/2830_3979_000007_000000|PREFACE +test-clean/2830/3980/2830_3980_000018_000001|Humble man that he was, he will not now take a back seat. +test-clean/2961/961/2961_961_000004_000037|Then your city did bravely, and won renown over the whole earth. +test-clean/2961/961/2961_961_000023_000003|But violent as were the internal and alimentary fluids, the tide became still more violent when the body came into contact with flaming fire, or the solid earth, or gliding waters, or the stormy wind; the motions produced by these impulses pass through the body to the soul and have the name of sensations. +test-clean/3570/5694/3570_5694_000009_000003|The canon of reputability is at hand and seizes upon such innovations as are, according to its standard, fit to survive. +test-clean/3570/5695/3570_5695_000001_000003|But the middle class wife still carries on the business of vicarious leisure, for the good name of the household and its master. +test-clean/3570/5695/3570_5695_000009_000005|Considered by itself simply-taken in the first degree-this added provocation to which the artisan and the urban laboring classes are exposed may not very seriously decrease the amount of savings; but in its cumulative action, through raising the standard of decent expenditure, its deterrent effect on the tendency to save cannot but be very great. +test-clean/3570/5696/3570_5696_000011_000006|For this is the basis of award of the instinct of workmanship, and that instinct is the court of final appeal in any question of economic truth or adequacy. +test-clean/3729/6852/3729_6852_000004_000003|In order to please her, I spoke to her of the Abbe Conti, and I had occasion to quote two lines of that profound writer. +test-clean/4077/13754/4077_13754_000002_000000|The troops, once in Utah, had to be provisioned; and everything the settlers could spare was eagerly bought at an unusual price. The gold changed hands. +test-clean/4446/2271/4446_2271_000003_000004|There's everything in seeing Hilda while she's fresh in a part. +test-clean/4446/2271/4446_2271_000020_000001|Lady Westmere is very fond of Hilda." +test-clean/4446/2273/4446_2273_000008_000002|I've no need for fine clothes in Mac's play this time, so I can afford a few duddies for myself. +test-clean/4446/2273/4446_2273_000027_000004|She did my blouses beautifully the last time I was there, and was so delighted to see me again. +test-clean/4446/2273/4446_2273_000046_000001|"Aren't you afraid to let the wind low like that on your neck? +test-clean/4446/2275/4446_2275_000013_000000|Hilda was pale by this time, and her eyes were wide with fright. +test-clean/4446/2275/4446_2275_000038_000006|"You want to tell me that you can only see me like this, as old friends do, or out in the world among people? +test-clean/4507/16021/4507_16021_000011_000000|It engenders a whole world, la pegre, for which read theft, and a hell, la pegrenne, for which read hunger. +test-clean/4507/16021/4507_16021_000030_000001|Facts form one of these, and ideas the other. +test-clean/4970/29093/4970_29093_000010_000000|Delightful illusion of paint and tinsel and silk attire, of cheap sentiment and high and mighty dialogue! +test-clean/4970/29093/4970_29093_000047_000000|"Never mind the map. +test-clean/4970/29095/4970_29095_000021_000000|"I will practice it." +test-clean/4970/29095/4970_29095_000055_000002|He took it with him from the Southern Hotel, when he went to walk, and read it over and again in an unfrequented street as he stumbled along. +test-clean/4992/41797/4992_41797_000014_000002|He keeps the thou shalt not commandments first rate, Hen Lord does! +test-clean/4992/41806/4992_41806_000020_000001|Thou who settest the solitary in families, bless the life that is sheltered here. +test-clean/5105/28241/5105_28241_000004_000004|The late astounding events, however, had rendered Procope manifestly uneasy, and not the less so from his consciousness that the count secretly partook of his own anxiety. +test-clean/5142/33396/5142_33396_000004_000004|At the prow I carved the head with open mouth and forked tongue thrust out. +test-clean/5142/33396/5142_33396_000039_000000|"The thralls were bringing in a great pot of meat. +test-clean/5142/36377/5142_36377_000013_000003|I liked Naomi Colebrook at first sight; liked her pleasant smile; liked her hearty shake of the hand when we were presented to each other. +test-clean/5639/40744/5639_40744_000003_000006|Mother! dear father! do you hear me? +test-clean/5639/40744/5639_40744_000022_000000|Just then Leocadia came to herself, and embracing the cross seemed changed into a sea of tears, and the gentleman remained in utter bewilderment, until his wife had repeated to him, from beginning to end, Leocadia's whole story; and he believed it, through the blessed dispensation of Heaven, which had confirmed it by so many convincing testimonies. +test-clean/5683/32865/5683_32865_000018_000000|Well, it was pretty-French, I dare say-a little set of tablets-a toy-the cover of enamel, studded in small jewels, with a slender border of symbolic flowers, and with a heart in the centre, a mosaic of little carbuncles, rubies, and other red and crimson stones, placed with a view to light and shade. +test-clean/5683/32866/5683_32866_000005_000000|'Did you see that?' said Wylder in my ear, with a chuckle; and, wagging his head, he added, rather loftily for him, 'Miss Brandon, I reckon, has taken your measure, Master Stanley, as well as i I wonder what the deuce the old dowager sees in him. +test-clean/5683/32866/5683_32866_000047_000002|I was not a bit afraid of being found out. +test-clean/5683/32879/5683_32879_000036_000002|Be he near, or be he far, I regard his very name with horror.' +test-clean/6829/68769/6829_68769_000011_000000|So as soon as breakfast was over the next morning Beth and Kenneth took one of the automobiles, the boy consenting unwillingly to this sort of locomotion because it would save much time. +test-clean/6829/68769/6829_68769_000051_000001|One morning she tried to light the fire with kerosene, and lost her sight. +test-clean/6829/68769/6829_68769_000089_000001|Why should you do all this?" +test-clean/6829/68771/6829_68771_000018_000003|A speakers' stand, profusely decorated, had been erected on the lawn, and hundreds of folding chairs provided for seats. +test-clean/6930/75918/6930_75918_000000_000001|Night. +test-clean/6930/81414/6930_81414_000041_000001|Here is his scarf, which has evidently been strained, and on it are spots of blood, while all around are marks indicating a struggle. +test-clean/7021/79740/7021_79740_000010_000006|I observe that, when you both wish for the same thing, you don't quarrel for it and try to pull it away from one another; but one waits like a lady until the other has done with it. +test-clean/7021/85628/7021_85628_000017_000000|"I am going to the court ball," answered Anders. +test-clean/7127/75946/7127_75946_000022_000002|It is necessary, therefore, that he should comply." +test-clean/7127/75946/7127_75946_000061_000001|Disdainful of a success of which Madame showed no acknowledgement, he thought of nothing but boldly regaining the marked preference of the princess. +test-clean/7127/75947/7127_75947_000035_000000|"Quite true, and I believe you are right. +test-clean/7176/88083/7176_88083_000002_000003|He was too imposing in appearance, too gorgeous in apparel, too bold and vigilant in demeanor to be so misunderstood. +test-clean/7176/88083/7176_88083_000017_000000|Immediately over his outstretched gleaming head flew the hawk. +test-clean/7176/92135/7176_92135_000011_000000|And, so on in the same vein for some thirty lines. +test-clean/7176/92135/7176_92135_000074_000001|Tea, please, Matthews. +test-clean/7729/102255/7729_102255_000011_000003|The Free State Hotel served as barracks. +test-clean/7729/102255/7729_102255_000028_000009|They were squads of Kansas militia, companies of "peaceful emigrants," or gangs of irresponsible outlaws, to suit the chance, the whim, or the need of the moment. +test-clean/8230/279154/8230_279154_000003_000002|In the present lecture I shall attempt the analysis of memory knowledge, both as an introduction to the problem of knowledge in general, and because memory, in some form, is presupposed in almost all other knowledge. +test-clean/8230/279154/8230_279154_000013_000003|One of these is context. +test-clean/8230/279154/8230_279154_000027_000000|A further stage is RECOGNITION. +test-clean/8455/210777/8455_210777_000022_000003|And immediately on his sitting down, there got up a gentleman to whom I had not been introduced before this day, and gave the health of Mrs Neverbend and the ladies of Britannula. +test-clean/8455/210777/8455_210777_000064_000001|Government that he shall be treated with all respect, and that those honours shall be paid to him which are due to the President of a friendly republic. +test-clean/8463/287645/8463_287645_000023_000001|For instance, Jacob Taylor was noticed on the record book as being twenty three years of age, and the name of his master was entered as "William Pollit;" but as Jacob had never been allowed to learn to read, he might have failed in giving a correct pronunciation of the name. +test-clean/8463/294825/8463_294825_000048_000000|- CENTIMETER Roughly two fifths of an inch +test-clean/8463/294828/8463_294828_000046_000001|Conseil did them in a flash, and I was sure the lad hadn't missed a thing, because he classified shirts and suits as expertly as birds and mammals. +test-clean/8555/284447/8555_284447_000018_000002|The poor Queen, by the way, was seldom seen, as she passed all her time playing solitaire with a deck that was one card short, hoping that before she had lived her entire six hundred years she would win the game. +test-clean/8555/284447/8555_284447_000049_000000|Now, indeed, the Boolooroo was as angry as he was amazed. +test-clean/8555/284449/8555_284449_000039_000000|When the courtiers and the people assembled saw the goat they gave a great cheer, for the beast had helped to dethrone their wicked Ruler. +test-clean/8555/292519/8555_292519_000041_000000|She was alone that night. He had broken into her courtyard. Above the gurgling gutters he heard- surely- a door unchained? diff --git a/src/third_party/BigVGAN/filelists/LibriTTS/test-other.txt b/src/third_party/BigVGAN/filelists/LibriTTS/test-other.txt new file mode 100644 index 0000000000000000000000000000000000000000..8e083a4fa6951c9c83b8c708b242592b9a8fa151 --- /dev/null +++ b/src/third_party/BigVGAN/filelists/LibriTTS/test-other.txt @@ -0,0 +1,103 @@ +test-other/1688/142285/1688_142285_000000_000000|'Margaret!' said mr Hale, as he returned from showing his guest downstairs; 'I could not help watching your face with some anxiety, when mr Thornton made his confession of having been a shop boy. +test-other/1688/142285/1688_142285_000046_000000|'No, mamma; that Anne Buckley would never have done.' +test-other/1998/15444/1998_15444_000012_000000|Simple filtration will sometimes suffice to separate the required substance; in other cases dialysis will be necessary, in order that crystalloid substances may be separated from colloid bodies. +test-other/1998/29454/1998_29454_000021_000001|Fried eggs and bacon-he had one egg and the man had three-bread and butter-and if the bread was thick, so was the butter-and as many cups of tea as you liked to say thank you for. +test-other/1998/29454/1998_29454_000053_000001|It almost looked, Dickie thought, as though he had brought them out for some special purpose. +test-other/1998/29455/1998_29455_000022_000000|It was a wonderful day. +test-other/1998/29455/1998_29455_000082_000003|But 'e's never let it out." +test-other/2414/128292/2414_128292_000003_000000|"What!" said he, "have not the most ludicrous things always happened to us old anchorites and saints? +test-other/2609/156975/2609_156975_000036_000004|The cruel fate of his people and the painful experience in Egypt that had driven him into the wilderness prepared his mind to receive this training. +test-other/3005/163389/3005_163389_000017_000001|And they laughed all the time, and that made the duke mad; and everybody left, anyway, before the show was over, but one boy which was asleep. +test-other/3005/163390/3005_163390_000023_000021|S'pose people left money laying around where he was what did he do? +test-other/3005/163391/3005_163391_000021_000000|"It's a pretty long journey. +test-other/3005/163399/3005_163399_000013_000002|When we got there she set me down in a split bottomed chair, and set herself down on a little low stool in front of me, holding both of my hands, and says: +test-other/3005/163399/3005_163399_000045_000000|He sprung to the window at the head of the bed, and that give mrs Phelps the chance she wanted. +test-other/3080/5040/3080_5040_000000_000010|You have no such ladies in Ireland? +test-other/3331/159605/3331_159605_000006_000002|I could do so much for all at home how I should enjoy that!" And Polly let her thoughts revel in the luxurious future her fancy painted. +test-other/3331/159605/3331_159605_000082_000000|"Who got up that nice idea, I should like to know?" demanded Polly, as Fanny stopped for breath. +test-other/3528/168656/3528_168656_000003_000003|She told wonders of the Abbey of Fontevrault,--that it was like a city, and that there were streets in the monastery. +test-other/3528/168669/3528_168669_000030_000000|A silence ensued. +test-other/3528/168669/3528_168669_000075_000000|"Like yourself, reverend Mother." +test-other/3528/168669/3528_168669_000123_000000|"But the commissary of police-" +test-other/3528/168669/3528_168669_000137_000000|"That is well." +test-other/3528/168669/3528_168669_000164_000008|I shall have my lever. +test-other/3538/142836/3538_142836_000021_000003|However, as late as the reigns of our two last Georges, fabulous sums were often expended upon fanciful desserts. +test-other/3538/163619/3538_163619_000054_000000|'Now he says that you are to make haste and throw yourself overboard,' answered the step mother. +test-other/3538/163622/3538_163622_000069_000000|So they travelled onwards again, for many and many a mile, over hill and dale. +test-other/3538/163624/3538_163624_000038_000000|Then Sigurd went down into that deep place, and dug many pits in it, and in one of the pits he lay hidden with his sword drawn. +test-other/367/130732/367_130732_000002_000001|Probably nowhere in San Francisco could one get lobster better served than in the Old Delmonico restaurant of the days before the fire. +test-other/3764/168670/3764_168670_000003_000000|"But you, Father Madeleine?" +test-other/3764/168670/3764_168670_000043_000000|"Yes." +test-other/3764/168670/3764_168670_000083_000005|He grumbled:-- +test-other/3764/168671/3764_168671_000012_000003|He did what he liked with him. +test-other/3764/168671/3764_168671_000046_000000|"Comrade!" cried Fauchelevent. +test-other/3997/180294/3997_180294_000023_000000|Then, when God allows love to a courtesan, that love, which at first seems like a pardon, becomes for her almost without penitence. +test-other/3997/180294/3997_180294_000065_000001|The count will be coming back, and there is nothing to be gained by his finding you here." +test-other/3997/180297/3997_180297_000034_000004|For these people we have to be merry when they are merry, well when they want to sup, sceptics like themselves. +test-other/3997/182399/3997_182399_000014_000003|Oh, my, no! +test-other/4198/61336/4198_61336_000000_000003|It is significant to note in this connection that the new king was an unswerving adherent of the cult of Ashur, by the adherents of which he was probably strongly supported. +test-other/4198/61336/4198_61336_000033_000001|Nabonassar had died and was succeeded by his son Nabu nadin zeri, who, after reigning for two years, was slain in a rebellion. +test-other/4294/14317/4294_14317_000022_000011|I do not condescend to smite you. He looked at me submissively and said nothing. +test-other/4294/35475/4294_35475_000018_000001|At last they reached a wide chasm that bounded the Ogre's domain. +test-other/4294/35475/4294_35475_000050_000002|They said, "We are only waiting to lay some wily plan to capture the Ogre." +test-other/4294/9934/4294_9934_000025_000000|"Gold; here it is." +test-other/4350/10919/4350_10919_000006_000000|"Immediately, princess. +test-other/4350/9170/4350_9170_000005_000001|Authority, in the sense in which the word is ordinarily understood, is a means of forcing a man to act in opposition to his desires. +test-other/4350/9170/4350_9170_000056_000000|But the fatal significance of universal military service, as the manifestation of the contradiction inherent in the social conception of life, is not only apparent in that. +test-other/4852/28311/4852_28311_000031_000001|After a step or two, not finding his friend beside him, he turned. +test-other/4852/28319/4852_28319_000013_000002|mr Wicker waited patiently beside him for a few moments for Chris to get up his courage. +test-other/533/1066/533_1066_000008_000000|"I mean," he persisted, "do you feel as though you could go through with something rather unusual?" +test-other/533/131562/533_131562_000018_000000|mr Huntingdon then went up stairs. +test-other/5442/41168/5442_41168_000002_000001|Sergey Ivanovitch, waiting till the malignant gentleman had finished speaking, said that he thought the best solution would be to refer to the act itself, and asked the secretary to find the act. +test-other/5442/41169/5442_41169_000003_000000|"He's such a blackguard! +test-other/5442/41169/5442_41169_000030_000000|"And with what he made he'd increase his stock, or buy some land for a trifle, and let it out in lots to the peasants," Levin added, smiling. He had evidently more than once come across those commercial calculations. +test-other/5484/24317/5484_24317_000040_000006|Let us hope that you will make this three leaved clover the luck promising four leaved one. +test-other/5484/24318/5484_24318_000015_000002|The blood of these innocent men would be on his head if he did not listen to her representations. +test-other/5484/24318/5484_24318_000068_000001|He was appearing before his companions only to give truth its just due. +test-other/5764/299665/5764_299665_000041_000004|He saw the seeds that man had planted wither and perish, but he sent no rain. +test-other/5764/299665/5764_299665_000070_000000|Think of the egotism of a man who believes that an infinite being wants his praise! +test-other/5764/299665/5764_299665_000102_000000|The first stone is that matter-substance-cannot be destroyed, cannot be annihilated. +test-other/5764/299665/5764_299665_000134_000000|You cannot reform these people with tracts and talk. +test-other/6070/63485/6070_63485_000025_000003|Hand me the cash, and I will hand you the pocketbook." +test-other/6070/86744/6070_86744_000027_000000|"Have you bachelor's apartments there? +test-other/6070/86745/6070_86745_000001_000002|Two windows only of the pavilion faced the street; three other windows looked into the court, and two at the back into the garden. +test-other/6128/63240/6128_63240_000012_000002|Neither five nor fifteen, and yet not ten exactly, but either nine or eleven. +test-other/6128/63240/6128_63240_000042_000002|mrs Luna explained to her sister that her freedom of speech was caused by his being a relation-though, indeed, he didn't seem to know much about them. +test-other/6128/63244/6128_63244_000002_000000|"I can't talk to those people, I can't!" said Olive Chancellor, with a face which seemed to plead for a remission of responsibility. +test-other/6432/63722/6432_63722_000026_000000|"Not the least in the world-not as much as you do," was the cool answer. +test-other/6432/63722/6432_63722_000050_000004|Queen Elizabeth was very fond of watches and clocks, and her friends, knowing that, used to present her with beautiful specimens. Some of the watches of her day were made in the form of crosses, purses, little books, and even skulls." +test-other/6432/63722/6432_63722_000080_000003|When it does it will create a sensation." +test-other/6432/63723/6432_63723_000026_000000|"No; but he will, or I'll sue him and get judgment. +test-other/6432/63723/6432_63723_000057_000000|"Then for the love of-" +test-other/6432/63723/6432_63723_000080_000000|"Hello, Harry! +test-other/6938/70848/6938_70848_000046_000003|Show me the source!" +test-other/6938/70848/6938_70848_000104_000000|With biting sarcasm he went on to speak of the Allied diplomats, till then contemptuous of Russia's invitation to an armistice, which had been accepted by the Central Powers. +test-other/7105/2330/7105_2330_000021_000000|"He won't go unless he has a brass band. +test-other/7105/2340/7105_2340_000015_000001|We feel that we must live on cream for the rest of our lives. +test-other/7902/96591/7902_96591_000008_000001|I did not come to frighten you; you frightened me." +test-other/7902/96591/7902_96591_000048_000000|"No," he thought to himself, "I don't believe they would kill me, but they would knock me about." +test-other/7902/96592/7902_96592_000024_000001|Once out of that room he could ran, and by daylight the smugglers dare not hunt him down. +test-other/7902/96592/7902_96592_000063_000000|"What for?" cried Ram. +test-other/7902/96594/7902_96594_000014_000001|These fellows are very cunning, but we shall be too many for them one of these days." +test-other/7902/96594/7902_96594_000062_000001|Keep a sharp look out on the cliff to see if Mr Raystoke is making signals for a boat. +test-other/7902/96595/7902_96595_000039_000000|The man shook his head, and stared as if he didn't half understand the drift of what was said. +test-other/7975/280057/7975_280057_000009_000000|Naturally we were Southerners in sympathy and in fact. +test-other/7975/280057/7975_280057_000025_000004|On reaching the camp the first person I saw whom I knew was Cole Younger. +test-other/7975/280076/7975_280076_000013_000001|I will give you this outline and sketch of my whereabouts and actions at the time of certain robberies with which I am charged. +test-other/7975/280084/7975_280084_000007_000000|But between the time we broke camp and the time they reached the bridge the three who went ahead drank a quart of whisky, and there was the initial blunder at Northfield. +test-other/7975/280085/7975_280085_000005_000002|Some of the boys wanted to kill him, on the theory that "dead men tell no tales," while others urged binding him and leaving him in the woods. +test-other/8131/117016/8131_117016_000005_000000|The Stonewall gang numbered perhaps five hundred. +test-other/8131/117016/8131_117016_000025_000001|"And don't let them get away!" +test-other/8131/117016/8131_117016_000047_000006|I can always go back to Earth, and I'll try to take you along. +test-other/8131/117017/8131_117017_000005_000000|Gordon hit the signal switch, and the Marspeaker let out a shrill whistle. +test-other/8131/117017/8131_117017_000020_000003|There's no graft out here." +test-other/8131/117029/8131_117029_000007_000002|Wrecks were being broken up, with salvageable material used for newer homes. Gordon came to a row of temporary bubbles, individual dwellings built like the dome, but opaque for privacy. +test-other/8131/117029/8131_117029_000023_000004|But there'll be pushers as long as weak men turn to drugs, and graft as long as voters allow the thing to get out of their hands. +test-other/8188/269288/8188_269288_000018_000000|A few moments later there came a tap at the door. +test-other/8188/269288/8188_269288_000053_000001|"Do you want to kill me? +test-other/8188/269290/8188_269290_000035_000001|"But now, Leslie, what is the trouble? +test-other/8188/269290/8188_269290_000065_000000|"I don't think she is quite well," replied Leslie. +test-other/8280/266249/8280_266249_000030_000000|The ladies were weary, and retired to their state rooms shortly after tea, but the gentlemen sought the open air again and paced the deck for some time. +test-other/8280/266249/8280_266249_000113_000000|It was the last game of cards for that trip. +test-other/8461/278226/8461_278226_000026_000000|Laura thanked the French artist and then took her husband's arm and walked away with him. +test-other/8461/281231/8461_281231_000029_000002|Before long the towering flames had surmounted every obstruction, and rose to the evening skies one huge and burning beacon, seen far and wide through the adjacent country; tower after tower crashed down, with blazing roof and rafter. diff --git a/src/third_party/BigVGAN/filelists/LibriTTS/train-full.txt b/src/third_party/BigVGAN/filelists/LibriTTS/train-full.txt new file mode 100644 index 0000000000000000000000000000000000000000..bb67c60480dc3adfaa1bb02fbbb894a54586a38c --- /dev/null +++ b/src/third_party/BigVGAN/filelists/LibriTTS/train-full.txt @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d7101f6536088ad095079582745073dc122eccea402044aeea9e09459cf2e6cc +size 52144137 diff --git a/src/third_party/BigVGAN/filelists/LibriTTS/val-full.txt b/src/third_party/BigVGAN/filelists/LibriTTS/val-full.txt new file mode 100644 index 0000000000000000000000000000000000000000..fcdf61dd6ece531ebe751ba20296dd69f9aa69c8 --- /dev/null +++ b/src/third_party/BigVGAN/filelists/LibriTTS/val-full.txt @@ -0,0 +1,119 @@ +train-clean-100/103/1241/103_1241_000000_000001|matthew Cuthbert is surprised +train-clean-100/1594/135914/1594_135914_000033_000001|He told them, that having taken refuge in a small village, he there fell sick; that some charitable peasants had taken care of him, but finding he did not recover, a camel driver had undertaken to carry him to the hospital at Bagdad. +train-clean-100/233/155990/233_155990_000018_000002|I did, however, receive aid from the Emperor of Germany. +train-clean-100/3240/131231/3240_131231_000041_000003|Some persons, thinking them to be sea fishes, placed them in salt water, according to mr Roberts. +train-clean-100/40/222/40_222_000026_000000|"No, read it yourself," cried Catherine, whose second thoughts were clearer. +train-clean-100/4406/16882/4406_16882_000014_000002|Then they set me upon a horse with my wounded child in my lap, and there being no furniture upon the horse's back, as we were going down a steep hill we both fell over the horse's head, at which they, like inhumane creatures, laughed, and rejoiced to see it, though I thought we should there have ended our days, as overcome with so many difficulties. +train-clean-100/5393/19218/5393_19218_000115_000000|"Where is it going then?" +train-clean-100/6147/34606/6147_34606_000013_000008|One was "a dancing master;" that is to say he made the rustics frisk about by pricking the calves of their legs with the point of his sword. +train-clean-100/6848/76049/6848_76049_000003_000007|But suppose she was not all ordinary female person.... +train-clean-100/7505/258964/7505_258964_000026_000007|During the Boer War horses and mules rose in price in the United States on account of British purchases. +train-clean-100/831/130739/831_130739_000015_000000|But enough of these revelations. +train-clean-100/887/123291/887_123291_000028_000000|Here the Professor laid hold of the fossil skeleton, and handled it with the skill of a dexterous showman. +train-clean-360/112/123216/112_123216_000035_000009|The wonderful day had come and Roy's violets had no place in it. +train-clean-360/1323/149236/1323_149236_000007_000004|It was vain to hope that mere words would quiet a nation which had not, in any age, been very amenable to control, and which was now agitated by hopes and resentments, such as great revolutions, following great oppressions, naturally engender. +train-clean-360/1463/134465/1463_134465_000058_000000|Both Sandy and I began to laugh. +train-clean-360/1748/1562/1748_1562_000067_000000|"Oh, Pocket, Pocket," said I; but by this time the party which had gone towards the house, rushed out again, shouting and screaming with laughter. +train-clean-360/1914/133440/1914_133440_000014_000001|With the last twenty or thirty feet of it a deadly nausea came upon me. +train-clean-360/207/143321/207_143321_000070_000002|The canoes were not on the river bank. +train-clean-360/2272/152267/2272_152267_000003_000001|After supper the knight shared his own bed with the leper. +train-clean-360/2517/135227/2517_135227_000006_000005|As I was anxious to witness some of their purely religious ceremonies, I wished to go. +train-clean-360/2709/158074/2709_158074_000054_000000|Meanwhile the women continued to protest. +train-clean-360/2929/86777/2929_86777_000009_000000|A long silence followed; the peach, like the grapes, fell to the ground. +train-clean-360/318/124224/318_124224_000022_000010|In spite of his prejudice against Edward, he could put himself into Mr Waller's place, and see the thing from his point of view. +train-clean-360/3368/170952/3368_170952_000006_000000|And can he be fearless of death, or will he choose death in battle rather than defeat and slavery, who believes the world below to be real and terrible? +train-clean-360/3549/9203/3549_9203_000005_000004|We must hope so. There are examples. +train-clean-360/3835/178028/3835_178028_000007_000001|That day Prince Vasili no longer boasted of his protege Kutuzov, but remained silent when the commander in chief was mentioned. +train-clean-360/3994/149798/3994_149798_000005_000002|Afterward we can visit the mountain and punish the cruel magician of the Flatheads." +train-clean-360/4257/6397/4257_6397_000009_000000|At that time Nostromo had been already long enough in the country to raise to the highest pitch Captain Mitchell's opinion of the extraordinary value of his discovery. +train-clean-360/454/134728/454_134728_000133_000000|After a week of physical anguish, Unrest and pain, and feverish heat, Toward the ending day a calm and lull comes on, Three hours of peace and soothing rest of brain. +train-clean-360/4848/28247/4848_28247_000026_000002|Had he gained this arduous height only to behold the rocks carpeted with ice and snow, and reaching interminably to the far off horizon? +train-clean-360/5039/1189/5039_1189_000091_000000|The Shaggy Man sat down again and seemed well pleased. +train-clean-360/5261/19373/5261_19373_000011_000001|Some cause was evidently at work on this distant planet, causing it to disagree with its motion as calculated according to the law of gravitation. +train-clean-360/5538/70919/5538_70919_000032_000001|Only one person in the world could have laid those discoloured pearls at his door in the dead of night. The black figure in the garden, with the chiffon fluttering about its head, was Evelina Grey-or what was left of her. +train-clean-360/5712/48848/5712_48848_000060_000003|Lily for the time had been raised to a pinnacle,--a pinnacle which might be dangerous, but which was, at any rate, lofty. +train-clean-360/5935/43322/5935_43322_000050_000002|I think too-yes, I think that on the whole the ritual is impressive. +train-clean-360/6115/58433/6115_58433_000007_000002|We must run the risk." +train-clean-360/6341/64956/6341_64956_000040_000000|"Why, papa, I thought we were going to have such a nice time, and she just spoiled it all." +train-clean-360/6509/67147/6509_67147_000028_000003|It "was n't done" in England. +train-clean-360/6694/70837/6694_70837_000027_000002|There an enormous smiling sailor stopped me, and when I showed my pass, just said, "If you were Saint Michael himself, comrade, you couldn't pass here!" Through the glass of the door I made out the distorted face and gesticulating arms of a French correspondent, locked in.... +train-clean-360/6956/76046/6956_76046_000055_000001|Twelve hundred, fifteen hundred millions perhaps." +train-clean-360/7145/87280/7145_87280_000004_000003|This modern Ulysses made a masterful effort, but alas! had no ships to carry him away, and no wax with which to fill his ears. +train-clean-360/7314/77782/7314_77782_000011_000000|"Well, then, what in thunder is the matter with you?" cried the Lawyer, irritated. +train-clean-360/7525/92915/7525_92915_000034_000001|It was desperate, too, and lasted nearly all day-and it was one of the important battles of the world, although the numbers engaged in it were not large. +train-clean-360/7754/108640/7754_108640_000001_000004|Was I aware-was I fully aware of the discrepancy between us? +train-clean-360/7909/106369/7909_106369_000006_000002|And Colchian Aea lies at the edge of Pontus and of the world." +train-clean-360/8011/280922/8011_280922_000009_000000|He stretched out his hand, and all at once stroked my cheek. +train-clean-360/8176/115046/8176_115046_000027_000001|"Bless my soul, I never can understand it!" +train-clean-360/8459/292347/8459_292347_000015_000000|A woman near Gort, in Galway, says: 'There is a boy, now, of the Cloran's; but I wouldn't for the world let them think I spoke of him; it's two years since he came from America, and since that time he never went to Mass, or to church, or to fairs, or to market, or to stand on the cross roads, or to hurling, or to nothing. +train-clean-360/8699/291107/8699_291107_000003_000005|He leaned closer over it, regardless of the thin choking haze that spread about his face. In his attitude there was a rigidity of controlled excitement out of keeping with the seeming harmlessness of the experiment. +train-clean-360/8855/283242/8855_283242_000061_000000|"That couldn't be helped, grannie. +train-other-500/102/129232/102_129232_000050_000000|Is it otherwise in the newest romance? +train-other-500/1124/134775/1124_134775_000087_000001|Some of them are enclosed only by hedges, which lends a cheerful aspect to the street. +train-other-500/1239/138254/1239_138254_000010_000001|It was past twelve when all preparations were finished. +train-other-500/1373/132103/1373_132103_000056_000000|So they moved on. +train-other-500/1566/153036/1566_153036_000087_000003|You enter the river close by the trees, and then keep straight for the pile of stones, which is some fifty yards higher up, for the ford crosses the river at an angle." +train-other-500/1653/142352/1653_142352_000005_000002|If he should not come! +train-other-500/1710/133294/1710_133294_000023_000000|When the Indians were the sole inhabitants of the wilds from whence they have since been expelled, their wants were few. +train-other-500/1773/139602/1773_139602_000032_000001|When the rabbit saw that the badger was getting well, he thought of another plan by which he could compass the creature's death. +train-other-500/1920/1793/1920_1793_000037_000001|She has a little Blenheim lapdog, that she loves a thousand times more than she ever will me!" +train-other-500/2067/143535/2067_143535_000009_000002|Indeed, there, to the left, was a stone shelf with a little ledge to it three inches or so high, and on the shelf lay what I took to be a corpse; at any rate, it looked like one, with something white thrown over it. +train-other-500/2208/11020/2208_11020_000037_000001|It's at my place over there.' +train-other-500/2312/157868/2312_157868_000019_000002|I am the manager of the theatre, and I'm thundering glad that your first play has been produced at the 'New York,' sir. +train-other-500/2485/151992/2485_151992_000028_000005|At last he looked up at his wife and said, in a gentle tone: +train-other-500/2587/54186/2587_54186_000015_000000|Concerning the work as a whole he wrote to Clara while in the throes of composition: "This music now in me, and always such beautiful melodies! +train-other-500/2740/288813/2740_288813_000018_000003|But Philip had kept him apart, had banked him off, and yet drained him to the dregs. +train-other-500/2943/171001/2943_171001_000122_000000|The sound of his voice pronouncing her name aroused her. +train-other-500/3063/138651/3063_138651_000028_000000|But, as may be imagined, the unfortunate john was as much surprised by this rencounter as the other two. +train-other-500/3172/166439/3172_166439_000050_000000|And now at last was clear a thing that had puzzled greatly-the mechanism of that opening process by which sphere became oval disk, pyramid a four pointed star and-as I had glimpsed in the play of the Little Things about Norhala, could see now so plainly in the Keeper-the blocks took this inverted cruciform shape. +train-other-500/331/132019/331_132019_000038_000000|"I say, this is folly! +train-other-500/3467/166570/3467_166570_000054_000001|Does he never mention Orlando?" +train-other-500/3587/140711/3587_140711_000015_000001|O fie, mrs Jervis, said I, how could you serve me so? Besides, it looks too free both in me, and to him. +train-other-500/3675/187020/3675_187020_000026_000001|"I wonder what would be suitable? +train-other-500/3819/134146/3819_134146_000019_000001|Also the figure half hidden by the cupboard door-was a female figure, massive, and in flowing robes. +train-other-500/3912/77626/3912_77626_000003_000004|You may almost distinguish the figures on the clock that has just told the hour. +train-other-500/4015/63729/4015_63729_000058_000000|"It does." +train-other-500/413/22436/413_22436_000035_000003|I conjecture, the French squadron is bound for Malta and Alexandria, and the Spanish fleet for the attack of Minorca." +train-other-500/4218/41159/4218_41159_000028_000002|Yes? That worries Alexey. +train-other-500/4352/10940/4352_10940_000037_000002|He doesn't exist." +train-other-500/4463/26871/4463_26871_000023_000000|"I did not notice him following me," she said timidly. +train-other-500/4591/14356/4591_14356_000019_000000|"Within three days," cried the enchanter, loudly, "bring Rinaldo and Ricciardetto into the pass of Ronces Valles. +train-other-500/4738/291957/4738_291957_000000_000001|ODE ON THE SPRING. +train-other-500/4824/36029/4824_36029_000045_000003|And indeed Janet herself had taken no part in the politics, content merely to advise the combatants upon their demeanour. +train-other-500/4936/65528/4936_65528_000014_000007|I immediately responded, "Yes, they are most terrible struck on each other," and I said it in a tone that indicated I thought it a most beautiful and lovely thing that they should be so. +train-other-500/5019/38670/5019_38670_000017_000000|"Let me make you a present of the gloves," she said, with her irresistible smile. +train-other-500/5132/33409/5132_33409_000016_000001|They waited on the table in Valhalla. +train-other-500/52/121057/52_121057_000019_000000|"I," cried the steward with a strange expression. +train-other-500/5321/53046/5321_53046_000025_000003|I gather from what mrs joel said that she's rather touched in her mind too, and has an awful hankering to get home here-to this very house. +train-other-500/5429/210770/5429_210770_000029_000006|But this was not all. +train-other-500/557/129797/557_129797_000072_000001|The guns were manned, the gunners already kindling fuses, when the buccaneer fleet, whilst still heading for Palomas, was observed to bear away to the west. +train-other-500/572/128861/572_128861_000016_000002|My home was desolate. +train-other-500/5826/53497/5826_53497_000044_000001|If it be as you say, he will have shown himself noble, and his nobility will have consisted in this, that he has been willing to take that which he does not want, in order that he may succour one whom he loves. +train-other-500/5906/52158/5906_52158_000055_000000|The impression that he gets this knowledge or suspicion from the outside is due, the scientists say, to the fact that his thinking has proceeded at such lightning like speed that he was unable to watch the wheels go round. +train-other-500/6009/57639/6009_57639_000038_000000|This, friendly reader, is my only motive. +train-other-500/6106/58196/6106_58196_000007_000001|I tell you that you must make the dress. +train-other-500/6178/86034/6178_86034_000079_000004|Then she will grow calmer, and will know you again. +train-other-500/6284/63091/6284_63091_000133_000001|I don't want to go anywhere where anybody'll see me." +train-other-500/6436/104980/6436_104980_000009_000002|I guess you never heard about this house." +train-other-500/6540/232291/6540_232291_000017_000003|The girl was not wholly a savage. +train-other-500/6627/67844/6627_67844_000046_000002|The other girls had stopped talking, and now looked at Sylvia as if wondering what she would say. +train-other-500/6707/77351/6707_77351_000002_000006|But our first words I may give you, because though they conveyed nothing to me at the time, afterwards they meant much. +train-other-500/6777/76694/6777_76694_000013_000011|When they are forcibly put out of Garraway's on Saturday night-which they must be, for they never would go out of their own accord-where do they vanish until Monday morning? +train-other-500/690/133452/690_133452_000011_000000|Campany lifted his quill pen and pointed to a case of big leather bound volumes in a far corner of the room. +train-other-500/7008/34667/7008_34667_000032_000002|What had happened? +train-other-500/7131/92815/7131_92815_000039_000001|The cabman tried to pass to the left, but a heavy express wagon cut him off. +train-other-500/7220/77911/7220_77911_000005_000000|"Do? +train-other-500/7326/245693/7326_245693_000008_000000|Whether the Appetite Is a Special Power of the Soul? +train-other-500/7392/105672/7392_105672_000013_000005|Whoever, being required, refused to answer upon oath to any article of this act of settlement, was declared to be guilty of treason; and by this clause a species of political inquisition was established in the kingdom, as well as the accusations of treason multiplied to an unreasonable degree. +train-other-500/7512/98636/7512_98636_000017_000002|A man thus rarely makes provision for the future, and looks with scorn on foreign customs which seem to betoken a fear lest, in old age, ungrateful children may neglect their parents and cast them aside. +train-other-500/7654/258963/7654_258963_000007_000007|Egypt, for a time reduced to a semi desert condition, has only in the past century been restored to a certain extent by the use of new methods and a return to the old ones. +train-other-500/7769/99396/7769_99396_000020_000002|I had to go out once a day in search of food. +train-other-500/791/127519/791_127519_000086_000000|This was how it came about. +train-other-500/8042/113769/8042_113769_000021_000000|House the second. +train-other-500/8180/274725/8180_274725_000010_000000|"What fools men are in love matters," quoth Patty to herself-"at least most men!" with a thought backward to Mark's sensible choosing. +train-other-500/8291/282929/8291_282929_000031_000006|He's in a devil of a-Well, he needs the money, and I've got to get it for him. You know my word's good, Cooper." +train-other-500/8389/120181/8389_120181_000022_000000|"No," I answered. +train-other-500/8476/269293/8476_269293_000078_000001|Annie, in some wonder, went downstairs alone. +train-other-500/8675/295195/8675_295195_000004_000004|Everything had gone on prosperously with them, and they had reared many successive families of young Nutcrackers, who went forth to assume their places in the forest of life, and to reflect credit on their bringing up,--so that naturally enough they began to have a very easy way of considering themselves models of wisdom. +train-other-500/9000/282381/9000_282381_000016_000008|Bank facings seemed to indicate that the richest pay dirt lay at bed rock. +train-other-500/978/132494/978_132494_000017_000001|And what made you come at that very minute? diff --git a/src/third_party/BigVGAN/incl_licenses/LICENSE_1 b/src/third_party/BigVGAN/incl_licenses/LICENSE_1 new file mode 100644 index 0000000000000000000000000000000000000000..774eaf8462a160c855fb01f069b3eb8164ce019c --- /dev/null +++ b/src/third_party/BigVGAN/incl_licenses/LICENSE_1 @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2020 Jungil Kong + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/src/third_party/BigVGAN/incl_licenses/LICENSE_2 b/src/third_party/BigVGAN/incl_licenses/LICENSE_2 new file mode 100644 index 0000000000000000000000000000000000000000..522989138d63bbdee9049e19d0cd81751f818c8a --- /dev/null +++ b/src/third_party/BigVGAN/incl_licenses/LICENSE_2 @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2020 Edward Dixon + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/src/third_party/BigVGAN/incl_licenses/LICENSE_3 b/src/third_party/BigVGAN/incl_licenses/LICENSE_3 new file mode 100644 index 0000000000000000000000000000000000000000..eeac88fb9dc15a1427b41173cf5f136327230c49 --- /dev/null +++ b/src/third_party/BigVGAN/incl_licenses/LICENSE_3 @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. \ No newline at end of file diff --git a/src/third_party/BigVGAN/incl_licenses/LICENSE_4 b/src/third_party/BigVGAN/incl_licenses/LICENSE_4 new file mode 100644 index 0000000000000000000000000000000000000000..f87b31c7f648a6863dbda41e3ef94aebcd5d6332 --- /dev/null +++ b/src/third_party/BigVGAN/incl_licenses/LICENSE_4 @@ -0,0 +1,29 @@ +BSD 3-Clause License + +Copyright (c) 2019, Seungwon Park 박승원 +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/src/third_party/BigVGAN/incl_licenses/LICENSE_5 b/src/third_party/BigVGAN/incl_licenses/LICENSE_5 new file mode 100644 index 0000000000000000000000000000000000000000..86ac396a9469809d46011859baeb1d34346ab9b5 --- /dev/null +++ b/src/third_party/BigVGAN/incl_licenses/LICENSE_5 @@ -0,0 +1,16 @@ +Copyright 2020 Alexandre Défossez + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +associated documentation files (the "Software"), to deal in the Software without restriction, +including without limitation the rights to use, copy, modify, merge, publish, distribute, +sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or +substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT +NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/src/third_party/BigVGAN/incl_licenses/LICENSE_6 b/src/third_party/BigVGAN/incl_licenses/LICENSE_6 new file mode 100644 index 0000000000000000000000000000000000000000..e206724f34e848226ac3aee3ccd8afdade11f345 --- /dev/null +++ b/src/third_party/BigVGAN/incl_licenses/LICENSE_6 @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023-present, Descript + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/src/third_party/BigVGAN/incl_licenses/LICENSE_7 b/src/third_party/BigVGAN/incl_licenses/LICENSE_7 new file mode 100644 index 0000000000000000000000000000000000000000..6ee845091a4e0a1569c45ce1bdd47170f674d476 --- /dev/null +++ b/src/third_party/BigVGAN/incl_licenses/LICENSE_7 @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 Charactr Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/src/third_party/BigVGAN/incl_licenses/LICENSE_8 b/src/third_party/BigVGAN/incl_licenses/LICENSE_8 new file mode 100644 index 0000000000000000000000000000000000000000..188a7c077e81918487a546520a57f9923aef3e25 --- /dev/null +++ b/src/third_party/BigVGAN/incl_licenses/LICENSE_8 @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 Amphion + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/src/third_party/BigVGAN/inference.py b/src/third_party/BigVGAN/inference.py new file mode 100644 index 0000000000000000000000000000000000000000..4a2f02e09263c7426cbc1179c60732b23696869d --- /dev/null +++ b/src/third_party/BigVGAN/inference.py @@ -0,0 +1,89 @@ +# Adapted from https://github.com/jik876/hifi-gan under the MIT license. +# LICENSE is in incl_licenses directory. + +from __future__ import absolute_import, division, print_function, unicode_literals + +import os +import argparse +import json +import torch +import librosa +from utils import load_checkpoint +from meldataset import get_mel_spectrogram +from scipy.io.wavfile import write +from env import AttrDict +from meldataset import MAX_WAV_VALUE +from bigvgan import BigVGAN as Generator + +h = None +device = None +torch.backends.cudnn.benchmark = False + + +def inference(a, h): + generator = Generator(h, use_cuda_kernel=a.use_cuda_kernel).to(device) + + state_dict_g = load_checkpoint(a.checkpoint_file, device) + generator.load_state_dict(state_dict_g["generator"]) + + filelist = os.listdir(a.input_wavs_dir) + + os.makedirs(a.output_dir, exist_ok=True) + + generator.eval() + generator.remove_weight_norm() + with torch.no_grad(): + for i, filname in enumerate(filelist): + # Load the ground truth audio and resample if necessary + wav, sr = librosa.load( + os.path.join(a.input_wavs_dir, filname), sr=h.sampling_rate, mono=True + ) + wav = torch.FloatTensor(wav).to(device) + # Compute mel spectrogram from the ground truth audio + x = get_mel_spectrogram(wav.unsqueeze(0), generator.h) + + y_g_hat = generator(x) + + audio = y_g_hat.squeeze() + audio = audio * MAX_WAV_VALUE + audio = audio.cpu().numpy().astype("int16") + + output_file = os.path.join( + a.output_dir, os.path.splitext(filname)[0] + "_generated.wav" + ) + write(output_file, h.sampling_rate, audio) + print(output_file) + + +def main(): + print("Initializing Inference Process..") + + parser = argparse.ArgumentParser() + parser.add_argument("--input_wavs_dir", default="test_files") + parser.add_argument("--output_dir", default="generated_files") + parser.add_argument("--checkpoint_file", required=True) + parser.add_argument("--use_cuda_kernel", action="store_true", default=False) + + a = parser.parse_args() + + config_file = os.path.join(os.path.split(a.checkpoint_file)[0], "config.json") + with open(config_file) as f: + data = f.read() + + global h + json_config = json.loads(data) + h = AttrDict(json_config) + + torch.manual_seed(h.seed) + global device + if torch.cuda.is_available(): + torch.cuda.manual_seed(h.seed) + device = torch.device("cuda") + else: + device = torch.device("cpu") + + inference(a, h) + + +if __name__ == "__main__": + main() diff --git a/src/third_party/BigVGAN/inference_e2e.py b/src/third_party/BigVGAN/inference_e2e.py new file mode 100644 index 0000000000000000000000000000000000000000..94c8ebe7a20c3edee6a58cd3a846bde8f42f0d46 --- /dev/null +++ b/src/third_party/BigVGAN/inference_e2e.py @@ -0,0 +1,102 @@ +# Adapted from https://github.com/jik876/hifi-gan under the MIT license. +# LICENSE is in incl_licenses directory. + +from __future__ import absolute_import, division, print_function, unicode_literals + +import glob +import os +import numpy as np +import argparse +import json +import torch +from scipy.io.wavfile import write +from env import AttrDict +from meldataset import MAX_WAV_VALUE +from bigvgan import BigVGAN as Generator + +h = None +device = None +torch.backends.cudnn.benchmark = False + + +def load_checkpoint(filepath, device): + assert os.path.isfile(filepath) + print(f"Loading '{filepath}'") + checkpoint_dict = torch.load(filepath, map_location=device) + print("Complete.") + return checkpoint_dict + + +def scan_checkpoint(cp_dir, prefix): + pattern = os.path.join(cp_dir, prefix + "*") + cp_list = glob.glob(pattern) + if len(cp_list) == 0: + return "" + return sorted(cp_list)[-1] + + +def inference(a, h): + generator = Generator(h, use_cuda_kernel=a.use_cuda_kernel).to(device) + + state_dict_g = load_checkpoint(a.checkpoint_file, device) + generator.load_state_dict(state_dict_g["generator"]) + + filelist = os.listdir(a.input_mels_dir) + + os.makedirs(a.output_dir, exist_ok=True) + + generator.eval() + generator.remove_weight_norm() + with torch.no_grad(): + for i, filname in enumerate(filelist): + # Load the mel spectrogram in .npy format + x = np.load(os.path.join(a.input_mels_dir, filname)) + x = torch.FloatTensor(x).to(device) + if len(x.shape) == 2: + x = x.unsqueeze(0) + + y_g_hat = generator(x) + + audio = y_g_hat.squeeze() + audio = audio * MAX_WAV_VALUE + audio = audio.cpu().numpy().astype("int16") + + output_file = os.path.join( + a.output_dir, os.path.splitext(filname)[0] + "_generated_e2e.wav" + ) + write(output_file, h.sampling_rate, audio) + print(output_file) + + +def main(): + print("Initializing Inference Process..") + + parser = argparse.ArgumentParser() + parser.add_argument("--input_mels_dir", default="test_mel_files") + parser.add_argument("--output_dir", default="generated_files_from_mel") + parser.add_argument("--checkpoint_file", required=True) + parser.add_argument("--use_cuda_kernel", action="store_true", default=False) + + a = parser.parse_args() + + config_file = os.path.join(os.path.split(a.checkpoint_file)[0], "config.json") + with open(config_file) as f: + data = f.read() + + global h + json_config = json.loads(data) + h = AttrDict(json_config) + + torch.manual_seed(h.seed) + global device + if torch.cuda.is_available(): + torch.cuda.manual_seed(h.seed) + device = torch.device("cuda") + else: + device = torch.device("cpu") + + inference(a, h) + + +if __name__ == "__main__": + main() diff --git a/src/third_party/BigVGAN/loss.py b/src/third_party/BigVGAN/loss.py new file mode 100644 index 0000000000000000000000000000000000000000..3fdfba26c66bd05566c4ba7946d2f1163eed2067 --- /dev/null +++ b/src/third_party/BigVGAN/loss.py @@ -0,0 +1,254 @@ +# Copyright (c) 2024 NVIDIA CORPORATION. +# Licensed under the MIT license. + +# Adapted from https://github.com/jik876/hifi-gan under the MIT license. +# LICENSE is in incl_licenses directory. + + +import torch +import torch.nn.functional as F +import torch.nn as nn +from librosa.filters import mel as librosa_mel_fn +from scipy import signal + +import typing +from typing import Optional, List, Union, Dict, Tuple +from collections import namedtuple +import math +import functools + + +# Adapted from https://github.com/descriptinc/descript-audio-codec/blob/main/dac/nn/loss.py under the MIT license. +# LICENSE is in incl_licenses directory. +class MultiScaleMelSpectrogramLoss(nn.Module): + """Compute distance between mel spectrograms. Can be used + in a multi-scale way. + + Parameters + ---------- + n_mels : List[int] + Number of mels per STFT, by default [5, 10, 20, 40, 80, 160, 320], + window_lengths : List[int], optional + Length of each window of each STFT, by default [32, 64, 128, 256, 512, 1024, 2048] + loss_fn : typing.Callable, optional + How to compare each loss, by default nn.L1Loss() + clamp_eps : float, optional + Clamp on the log magnitude, below, by default 1e-5 + mag_weight : float, optional + Weight of raw magnitude portion of loss, by default 0.0 (no ampliciation on mag part) + log_weight : float, optional + Weight of log magnitude portion of loss, by default 1.0 + pow : float, optional + Power to raise magnitude to before taking log, by default 1.0 + weight : float, optional + Weight of this loss, by default 1.0 + match_stride : bool, optional + Whether to match the stride of convolutional layers, by default False + + Implementation copied from: https://github.com/descriptinc/lyrebird-audiotools/blob/961786aa1a9d628cca0c0486e5885a457fe70c1a/audiotools/metrics/spectral.py + Additional code copied and modified from https://github.com/descriptinc/audiotools/blob/master/audiotools/core/audio_signal.py + """ + + def __init__( + self, + sampling_rate: int, + n_mels: List[int] = [5, 10, 20, 40, 80, 160, 320], + window_lengths: List[int] = [32, 64, 128, 256, 512, 1024, 2048], + loss_fn: typing.Callable = nn.L1Loss(), + clamp_eps: float = 1e-5, + mag_weight: float = 0.0, + log_weight: float = 1.0, + pow: float = 1.0, + weight: float = 1.0, + match_stride: bool = False, + mel_fmin: List[float] = [0, 0, 0, 0, 0, 0, 0], + mel_fmax: List[float] = [None, None, None, None, None, None, None], + window_type: str = "hann", + ): + super().__init__() + self.sampling_rate = sampling_rate + + STFTParams = namedtuple( + "STFTParams", + ["window_length", "hop_length", "window_type", "match_stride"], + ) + + self.stft_params = [ + STFTParams( + window_length=w, + hop_length=w // 4, + match_stride=match_stride, + window_type=window_type, + ) + for w in window_lengths + ] + self.n_mels = n_mels + self.loss_fn = loss_fn + self.clamp_eps = clamp_eps + self.log_weight = log_weight + self.mag_weight = mag_weight + self.weight = weight + self.mel_fmin = mel_fmin + self.mel_fmax = mel_fmax + self.pow = pow + + @staticmethod + @functools.lru_cache(None) + def get_window( + window_type, + window_length, + ): + return signal.get_window(window_type, window_length) + + @staticmethod + @functools.lru_cache(None) + def get_mel_filters(sr, n_fft, n_mels, fmin, fmax): + return librosa_mel_fn(sr=sr, n_fft=n_fft, n_mels=n_mels, fmin=fmin, fmax=fmax) + + def mel_spectrogram( + self, + wav, + n_mels, + fmin, + fmax, + window_length, + hop_length, + match_stride, + window_type, + ): + """ + Mirrors AudioSignal.mel_spectrogram used by BigVGAN-v2 training from: + https://github.com/descriptinc/audiotools/blob/master/audiotools/core/audio_signal.py + """ + B, C, T = wav.shape + + if match_stride: + assert ( + hop_length == window_length // 4 + ), "For match_stride, hop must equal n_fft // 4" + right_pad = math.ceil(T / hop_length) * hop_length - T + pad = (window_length - hop_length) // 2 + else: + right_pad = 0 + pad = 0 + + wav = torch.nn.functional.pad(wav, (pad, pad + right_pad), mode="reflect") + + window = self.get_window(window_type, window_length) + window = torch.from_numpy(window).to(wav.device).float() + + stft = torch.stft( + wav.reshape(-1, T), + n_fft=window_length, + hop_length=hop_length, + window=window, + return_complex=True, + center=True, + ) + _, nf, nt = stft.shape + stft = stft.reshape(B, C, nf, nt) + if match_stride: + """ + Drop first two and last two frames, which are added, because of padding. Now num_frames * hop_length = num_samples. + """ + stft = stft[..., 2:-2] + magnitude = torch.abs(stft) + + nf = magnitude.shape[2] + mel_basis = self.get_mel_filters( + self.sampling_rate, 2 * (nf - 1), n_mels, fmin, fmax + ) + mel_basis = torch.from_numpy(mel_basis).to(wav.device) + mel_spectrogram = magnitude.transpose(2, -1) @ mel_basis.T + mel_spectrogram = mel_spectrogram.transpose(-1, 2) + + return mel_spectrogram + + def forward(self, x: torch.Tensor, y: torch.Tensor) -> torch.Tensor: + """Computes mel loss between an estimate and a reference + signal. + + Parameters + ---------- + x : torch.Tensor + Estimate signal + y : torch.Tensor + Reference signal + + Returns + ------- + torch.Tensor + Mel loss. + """ + + loss = 0.0 + for n_mels, fmin, fmax, s in zip( + self.n_mels, self.mel_fmin, self.mel_fmax, self.stft_params + ): + kwargs = { + "n_mels": n_mels, + "fmin": fmin, + "fmax": fmax, + "window_length": s.window_length, + "hop_length": s.hop_length, + "match_stride": s.match_stride, + "window_type": s.window_type, + } + + x_mels = self.mel_spectrogram(x, **kwargs) + y_mels = self.mel_spectrogram(y, **kwargs) + x_logmels = torch.log( + x_mels.clamp(min=self.clamp_eps).pow(self.pow) + ) / torch.log(torch.tensor(10.0)) + y_logmels = torch.log( + y_mels.clamp(min=self.clamp_eps).pow(self.pow) + ) / torch.log(torch.tensor(10.0)) + + loss += self.log_weight * self.loss_fn(x_logmels, y_logmels) + loss += self.mag_weight * self.loss_fn(x_logmels, y_logmels) + + return loss + + +# Loss functions +def feature_loss( + fmap_r: List[List[torch.Tensor]], fmap_g: List[List[torch.Tensor]] +) -> torch.Tensor: + + loss = 0 + for dr, dg in zip(fmap_r, fmap_g): + for rl, gl in zip(dr, dg): + loss += torch.mean(torch.abs(rl - gl)) + + return loss * 2 # This equates to lambda=2.0 for the feature matching loss + + +def discriminator_loss( + disc_real_outputs: List[torch.Tensor], disc_generated_outputs: List[torch.Tensor] +) -> Tuple[torch.Tensor, List[torch.Tensor], List[torch.Tensor]]: + + loss = 0 + r_losses = [] + g_losses = [] + for dr, dg in zip(disc_real_outputs, disc_generated_outputs): + r_loss = torch.mean((1 - dr) ** 2) + g_loss = torch.mean(dg**2) + loss += r_loss + g_loss + r_losses.append(r_loss.item()) + g_losses.append(g_loss.item()) + + return loss, r_losses, g_losses + + +def generator_loss( + disc_outputs: List[torch.Tensor], +) -> Tuple[torch.Tensor, List[torch.Tensor]]: + + loss = 0 + gen_losses = [] + for dg in disc_outputs: + l = torch.mean((1 - dg) ** 2) + gen_losses.append(l) + loss += l + + return loss, gen_losses diff --git a/src/third_party/BigVGAN/meldataset.py b/src/third_party/BigVGAN/meldataset.py new file mode 100644 index 0000000000000000000000000000000000000000..9661bb2f5e04835f09b1d6e318e3360094d187b4 --- /dev/null +++ b/src/third_party/BigVGAN/meldataset.py @@ -0,0 +1,396 @@ +# Copyright (c) 2024 NVIDIA CORPORATION. +# Licensed under the MIT license. + +# Adapted from https://github.com/jik876/hifi-gan under the MIT license. +# LICENSE is in incl_licenses directory. + +import math +import os +import random +import torch +import torch.utils.data +import numpy as np +import librosa +from librosa.filters import mel as librosa_mel_fn +import pathlib +from tqdm import tqdm +from typing import List, Tuple, Optional +from env import AttrDict + +MAX_WAV_VALUE = 32767.0 # NOTE: 32768.0 -1 to prevent int16 overflow (results in popping sound in corner cases) + + +def dynamic_range_compression(x, C=1, clip_val=1e-5): + return np.log(np.clip(x, a_min=clip_val, a_max=None) * C) + + +def dynamic_range_decompression(x, C=1): + return np.exp(x) / C + + +def dynamic_range_compression_torch(x, C=1, clip_val=1e-5): + return torch.log(torch.clamp(x, min=clip_val) * C) + + +def dynamic_range_decompression_torch(x, C=1): + return torch.exp(x) / C + + +def spectral_normalize_torch(magnitudes): + return dynamic_range_compression_torch(magnitudes) + + +def spectral_de_normalize_torch(magnitudes): + return dynamic_range_decompression_torch(magnitudes) + + +mel_basis_cache = {} +hann_window_cache = {} + + +def mel_spectrogram( + y: torch.Tensor, + n_fft: int, + num_mels: int, + sampling_rate: int, + hop_size: int, + win_size: int, + fmin: int, + fmax: int = None, + center: bool = False, +) -> torch.Tensor: + """ + Calculate the mel spectrogram of an input signal. + This function uses slaney norm for the librosa mel filterbank (using librosa.filters.mel) and uses Hann window for STFT (using torch.stft). + + Args: + y (torch.Tensor): Input signal. + n_fft (int): FFT size. + num_mels (int): Number of mel bins. + sampling_rate (int): Sampling rate of the input signal. + hop_size (int): Hop size for STFT. + win_size (int): Window size for STFT. + fmin (int): Minimum frequency for mel filterbank. + fmax (int): Maximum frequency for mel filterbank. If None, defaults to half the sampling rate (fmax = sr / 2.0) inside librosa_mel_fn + center (bool): Whether to pad the input to center the frames. Default is False. + + Returns: + torch.Tensor: Mel spectrogram. + """ + if torch.min(y) < -1.0: + print(f"[WARNING] Min value of input waveform signal is {torch.min(y)}") + if torch.max(y) > 1.0: + print(f"[WARNING] Max value of input waveform signal is {torch.max(y)}") + + device = y.device + key = f"{n_fft}_{num_mels}_{sampling_rate}_{hop_size}_{win_size}_{fmin}_{fmax}_{device}" + + if key not in mel_basis_cache: + mel = librosa_mel_fn( + sr=sampling_rate, n_fft=n_fft, n_mels=num_mels, fmin=fmin, fmax=fmax + ) + mel_basis_cache[key] = torch.from_numpy(mel).float().to(device) + hann_window_cache[key] = torch.hann_window(win_size).to(device) + + mel_basis = mel_basis_cache[key] + hann_window = hann_window_cache[key] + + padding = (n_fft - hop_size) // 2 + y = torch.nn.functional.pad( + y.unsqueeze(1), (padding, padding), mode="reflect" + ).squeeze(1) + + spec = torch.stft( + y, + n_fft, + hop_length=hop_size, + win_length=win_size, + window=hann_window, + center=center, + pad_mode="reflect", + normalized=False, + onesided=True, + return_complex=True, + ) + spec = torch.sqrt(torch.view_as_real(spec).pow(2).sum(-1) + 1e-9) + + mel_spec = torch.matmul(mel_basis, spec) + mel_spec = spectral_normalize_torch(mel_spec) + + return mel_spec + + +def get_mel_spectrogram(wav, h): + """ + Generate mel spectrogram from a waveform using given hyperparameters. + + Args: + wav (torch.Tensor): Input waveform. + h: Hyperparameters object with attributes n_fft, num_mels, sampling_rate, hop_size, win_size, fmin, fmax. + + Returns: + torch.Tensor: Mel spectrogram. + """ + return mel_spectrogram( + wav, + h.n_fft, + h.num_mels, + h.sampling_rate, + h.hop_size, + h.win_size, + h.fmin, + h.fmax, + ) + + +def get_dataset_filelist(a): + training_files = [] + validation_files = [] + list_unseen_validation_files = [] + + with open(a.input_training_file, "r", encoding="utf-8") as fi: + training_files = [ + os.path.join(a.input_wavs_dir, x.split("|")[0] + ".wav") + for x in fi.read().split("\n") + if len(x) > 0 + ] + print(f"first training file: {training_files[0]}") + + with open(a.input_validation_file, "r", encoding="utf-8") as fi: + validation_files = [ + os.path.join(a.input_wavs_dir, x.split("|")[0] + ".wav") + for x in fi.read().split("\n") + if len(x) > 0 + ] + print(f"first validation file: {validation_files[0]}") + + for i in range(len(a.list_input_unseen_validation_file)): + with open(a.list_input_unseen_validation_file[i], "r", encoding="utf-8") as fi: + unseen_validation_files = [ + os.path.join(a.list_input_unseen_wavs_dir[i], x.split("|")[0] + ".wav") + for x in fi.read().split("\n") + if len(x) > 0 + ] + print( + f"first unseen {i}th validation fileset: {unseen_validation_files[0]}" + ) + list_unseen_validation_files.append(unseen_validation_files) + + return training_files, validation_files, list_unseen_validation_files + + +class MelDataset(torch.utils.data.Dataset): + def __init__( + self, + training_files: List[str], + hparams: AttrDict, + segment_size: int, + n_fft: int, + num_mels: int, + hop_size: int, + win_size: int, + sampling_rate: int, + fmin: int, + fmax: Optional[int], + split: bool = True, + shuffle: bool = True, + device: str = None, + fmax_loss: Optional[int] = None, + fine_tuning: bool = False, + base_mels_path: str = None, + is_seen: bool = True, + ): + self.audio_files = training_files + random.seed(1234) + if shuffle: + random.shuffle(self.audio_files) + self.hparams = hparams + self.is_seen = is_seen + if self.is_seen: + self.name = pathlib.Path(self.audio_files[0]).parts[0] + else: + self.name = "-".join(pathlib.Path(self.audio_files[0]).parts[:2]).strip("/") + + self.segment_size = segment_size + self.sampling_rate = sampling_rate + self.split = split + self.n_fft = n_fft + self.num_mels = num_mels + self.hop_size = hop_size + self.win_size = win_size + self.fmin = fmin + self.fmax = fmax + self.fmax_loss = fmax_loss + self.device = device + self.fine_tuning = fine_tuning + self.base_mels_path = base_mels_path + + print("[INFO] checking dataset integrity...") + for i in tqdm(range(len(self.audio_files))): + assert os.path.exists( + self.audio_files[i] + ), f"{self.audio_files[i]} not found" + + def __getitem__( + self, index: int + ) -> Tuple[torch.Tensor, torch.Tensor, str, torch.Tensor]: + try: + filename = self.audio_files[index] + + # Use librosa.load that ensures loading waveform into mono with [-1, 1] float values + # Audio is ndarray with shape [T_time]. Disable auto-resampling here to minimize overhead + # The on-the-fly resampling during training will be done only for the obtained random chunk + audio, source_sampling_rate = librosa.load(filename, sr=None, mono=True) + + # Main logic that uses pair for training BigVGAN + if not self.fine_tuning: + if self.split: # Training step + # Obtain randomized audio chunk + if source_sampling_rate != self.sampling_rate: + # Adjust segment size to crop if the source sr is different + target_segment_size = math.ceil( + self.segment_size + * (source_sampling_rate / self.sampling_rate) + ) + else: + target_segment_size = self.segment_size + + # Compute upper bound index for the random chunk + random_chunk_upper_bound = max( + 0, audio.shape[0] - target_segment_size + ) + + # Crop or pad audio to obtain random chunk with target_segment_size + if audio.shape[0] >= target_segment_size: + audio_start = random.randint(0, random_chunk_upper_bound) + audio = audio[audio_start : audio_start + target_segment_size] + else: + audio = np.pad( + audio, + (0, target_segment_size - audio.shape[0]), + mode="constant", + ) + + # Resample audio chunk to self.sampling rate + if source_sampling_rate != self.sampling_rate: + audio = librosa.resample( + audio, + orig_sr=source_sampling_rate, + target_sr=self.sampling_rate, + ) + if audio.shape[0] > self.segment_size: + # trim last elements to match self.segment_size (e.g., 16385 for 44khz downsampled to 24khz -> 16384) + audio = audio[: self.segment_size] + + else: # Validation step + # Resample full audio clip to target sampling rate + if source_sampling_rate != self.sampling_rate: + audio = librosa.resample( + audio, + orig_sr=source_sampling_rate, + target_sr=self.sampling_rate, + ) + # Trim last elements to match audio length to self.hop_size * n for evaluation + if (audio.shape[0] % self.hop_size) != 0: + audio = audio[: -(audio.shape[0] % self.hop_size)] + + # BigVGAN is trained using volume-normalized waveform + audio = librosa.util.normalize(audio) * 0.95 + + # Cast ndarray to torch tensor + audio = torch.FloatTensor(audio) + audio = audio.unsqueeze(0) # [B(1), self.segment_size] + + # Compute mel spectrogram corresponding to audio + mel = mel_spectrogram( + audio, + self.n_fft, + self.num_mels, + self.sampling_rate, + self.hop_size, + self.win_size, + self.fmin, + self.fmax, + center=False, + ) # [B(1), self.num_mels, self.segment_size // self.hop_size] + + # Fine-tuning logic that uses pre-computed mel. Example: Using TTS model-generated mel as input + else: + # For fine-tuning, assert that the waveform is in the defined sampling_rate + # Fine-tuning won't support on-the-fly resampling to be fool-proof (the dataset should have been prepared properly) + assert ( + source_sampling_rate == self.sampling_rate + ), f"For fine_tuning, waveform must be in the spcified sampling rate {self.sampling_rate}, got {source_sampling_rate}" + + # Cast ndarray to torch tensor + audio = torch.FloatTensor(audio) + audio = audio.unsqueeze(0) # [B(1), T_time] + + # Load pre-computed mel from disk + mel = np.load( + os.path.join( + self.base_mels_path, + os.path.splitext(os.path.split(filename)[-1])[0] + ".npy", + ) + ) + mel = torch.from_numpy(mel) + + if len(mel.shape) < 3: + mel = mel.unsqueeze(0) # ensure [B, C, T] + + if self.split: + frames_per_seg = math.ceil(self.segment_size / self.hop_size) + + if audio.size(1) >= self.segment_size: + mel_start = random.randint(0, mel.size(2) - frames_per_seg - 1) + mel = mel[:, :, mel_start : mel_start + frames_per_seg] + audio = audio[ + :, + mel_start + * self.hop_size : (mel_start + frames_per_seg) + * self.hop_size, + ] + + # Pad pre-computed mel and audio to match length to ensuring fine-tuning without error. + # NOTE: this may introduce a single-frame misalignment of the + # To remove possible misalignment, it is recommended to prepare the pair where the audio length is the integer multiple of self.hop_size + mel = torch.nn.functional.pad( + mel, (0, frames_per_seg - mel.size(2)), "constant" + ) + audio = torch.nn.functional.pad( + audio, (0, self.segment_size - audio.size(1)), "constant" + ) + + # Compute mel_loss used by spectral regression objective. Uses self.fmax_loss instead (usually None) + mel_loss = mel_spectrogram( + audio, + self.n_fft, + self.num_mels, + self.sampling_rate, + self.hop_size, + self.win_size, + self.fmin, + self.fmax_loss, + center=False, + ) # [B(1), self.num_mels, self.segment_size // self.hop_size] + + # Shape sanity checks + assert ( + audio.shape[1] == mel.shape[2] * self.hop_size + and audio.shape[1] == mel_loss.shape[2] * self.hop_size + ), f"Audio length must be mel frame length * hop_size. Got audio shape {audio.shape} mel shape {mel.shape} mel_loss shape {mel_loss.shape}" + + return (mel.squeeze(), audio.squeeze(0), filename, mel_loss.squeeze()) + + # If it encounters error during loading the data, skip this sample and load random other sample to the batch + except Exception as e: + if self.fine_tuning: + raise e # Terminate training if it is fine-tuning. The dataset should have been prepared properly. + else: + print( + f"[WARNING] Failed to load waveform, skipping! filename: {filename} Error: {e}" + ) + return self[random.randrange(len(self))] + + def __len__(self): + return len(self.audio_files) diff --git a/src/third_party/BigVGAN/nv-modelcard++/.gitkeep b/src/third_party/BigVGAN/nv-modelcard++/.gitkeep new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/src/third_party/BigVGAN/nv-modelcard++/bias.md b/src/third_party/BigVGAN/nv-modelcard++/bias.md new file mode 100644 index 0000000000000000000000000000000000000000..68db9cd0a424393e3c2fb8bdf52c194e27ac4e8b --- /dev/null +++ b/src/third_party/BigVGAN/nv-modelcard++/bias.md @@ -0,0 +1,4 @@ +| Field | Response | +| :--------------------------------------------------------------------------------------------------------- | :--------------------------------------------------- | +| Participation considerations from adversely impacted groups protected classes in model design and testing: | None | +| Measures taken to mitigate against unwanted bias: | No measures taken to mitigate against unwanted bias. | diff --git a/src/third_party/BigVGAN/nv-modelcard++/explainability.md b/src/third_party/BigVGAN/nv-modelcard++/explainability.md new file mode 100644 index 0000000000000000000000000000000000000000..26a6ccf26376d60ee43d4717b5bbefcfea07e0d5 --- /dev/null +++ b/src/third_party/BigVGAN/nv-modelcard++/explainability.md @@ -0,0 +1,13 @@ +| Field | Response | +| :---------------------------------------------------------------------------------------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Intended Application & Domain: | Generating waveform from mel spectrogram. | +| Model Type: | Convolutional Neural Network (CNN) | +| Intended Users: | This model is intended for developers to synthesize and generate waveforms from the AI-generated mel spectrograms. | +| Output: | Audio Waveform | +| Describe how the model works: | Model generates audio waveform corresponding to the input mel spectrogram. | +| Name the adversely impacted groups this has been tested to deliver comparable outcomes regardless of: | Not Applicable | +| Technical Limitations: | This may not perform well on synthetically-generated mel spectrograms that deviate significantly from the profile of mel spectrograms on which this was trained. | +| Verified to have met prescribed NVIDIA quality standards: | Yes | +| Performance Metrics: | Perceptual Evaluation of Speech Quality (PESQ), Virtual Speech Quality Objective Listener (VISQOL), Multi-resolution STFT (MRSTFT), Mel cepstral distortion (MCD), Periodicity RMSE, Voice/Unvoiced F1 Score (V/UV F1) | +| Potential Known Risks: | This model may generate low-quality or distorted soundwaves. | +| Licensing: | https://github.com/NVIDIA/BigVGAN/blob/main/LICENSE | diff --git a/src/third_party/BigVGAN/nv-modelcard++/overview.md b/src/third_party/BigVGAN/nv-modelcard++/overview.md new file mode 100644 index 0000000000000000000000000000000000000000..681d18df3e38c4400f11e522cbb3c55b06809d8b --- /dev/null +++ b/src/third_party/BigVGAN/nv-modelcard++/overview.md @@ -0,0 +1,126 @@ +# Model Overview + +## Description: + +BigVGAN is a generative AI model specialized in synthesizing audio waveforms using Mel spectrogram as inputs. + +
+ +BigVGAN is a fully convolutional architecture with several upsampling blocks using transposed convolution followed by multiple residual dilated convolution layers. + +BigVGAN consists of a novel module, called anti-aliased multi-periodicity composition (AMP), which is specifically designed for generating waveforms. AMP is specialized in synthesizing high-frequency and periodic soundwaves drawing inspiration from audio signal processing principles. + +It applies a periodic activation function, called Snake, which provides an inductive bias to the architecture in generating periodic soundwaves. It also applies anti-aliasing filters to reduce undesired artifacts in the generated waveforms.
+ +This model is ready for commercial use.
+ +## References(s): + +- [BigVGAN: A Universal Neural Vocoder with Large-Scale Training](https://arxiv.org/abs/2206.04658)
+- [Project Page](https://research.nvidia.com/labs/adlr/projects/bigvgan/)
+- [Audio Demo](https://bigvgan-demo.github.io/)
+ +## Model Architecture: + +**Architecture Type:** Convolution Neural Network (CNN)
+**Network Architecture:** You can see the details of this model on this link: https://github.com/NVIDIA/BigVGAN and the related paper can be found here: https://arxiv.org/abs/2206.04658
+**Model Version:** 2.0
+ +## Input: + +**Input Type:** Audio
+**Input Format:** Mel Spectrogram
+**Input Parameters:** None
+**Other Properties Related to Input:** The input mel spectrogram has shape `[batch, channels, frames]`, where `channels` refers to the number of mel bands defined by the model and `frames` refers to the temporal length. The model supports arbitrary long `frames` that fits into the GPU memory. + +## Output: + +**Input Type:** Audio
+**Output Format:** Audio Waveform
+**Output Parameters:** None
+**Other Properties Related to Output:** The output audio waveform has shape `[batch, 1, time]`, where `1` refers to the mono audio channels and `time` refers to the temporal length. `time` is defined as a fixed integer multiple of input `frames`, which is an upsampling ratio of the model (`time = upsampling ratio * frames`). The output audio waveform consitutes float values with a range of `[-1, 1]`. + +## Software Integration: + +**Runtime Engine(s):** PyTorch + +**Supported Hardware Microarchitecture Compatibility:** NVIDIA Ampere, NVIDIA Hopper, NVIDIA Lovelace, NVIDIA Turing, NVIDIA Volta
+ +## Preferred/Supported Operating System(s): + +Linux + +## Model Version(s): + +v2.0 + +## Training, Testing, and Evaluation Datasets: + +### Training Dataset: + +The dataset contains diverse audio types, including speech in multiple languages, environmental sounds, and instruments. + +**Links:** + +- [AAM: Artificial Audio Multitracks Dataset](https://zenodo.org/records/5794629) +- [AudioCaps](https://audiocaps.github.io/) +- [AudioSet](https://research.google.com/audioset/index.html) +- [common-accent](https://huggingface.co/datasets/DTU54DL/common-accent) +- [Crowd Sourced Emotional Multimodal Actors Dataset (CREMA-D)](https://ieeexplore.ieee.org/document/6849440) +- [DCASE2017 Challenge, Task 4: Large-scale weakly supervised sound event detection for smart cars](https://dcase.community/challenge2017/task-large-scale-sound-event-detection) +- [FSDnoisy18k](https://zenodo.org/records/2529934) +- [Free Universal Sound Separation Dataset](https://zenodo.org/records/3694384) +- [Greatest Hits dataset](https://andrewowens.com/vis/) +- [GTZAN](https://ieeexplore.ieee.org/document/1021072) +- [JL corpus](https://www.kaggle.com/datasets/tli725/jl-corpus) +- [Medley-solos-DB: a cross-collection dataset for musical instrument recognition](https://zenodo.org/records/3464194) +- [MUSAN: A Music, Speech, and Noise Corpus](https://www.openslr.org/17/) +- [MusicBench](https://huggingface.co/datasets/amaai-lab/MusicBench) +- [MusicCaps](https://www.kaggle.com/datasets/googleai/musiccaps) +- [MusicNet](https://www.kaggle.com/datasets/imsparsh/musicnet-dataset) +- [NSynth](https://magenta.tensorflow.org/datasets/nsynth) +- [OnAir-Music-Dataset](https://github.com/sevagh/OnAir-Music-Dataset) +- [Audio Piano Triads Dataset](https://zenodo.org/records/4740877) +- [Pitch Audio Dataset (Surge synthesizer)](https://zenodo.org/records/4677097) +- [SONYC Urban Sound Tagging (SONYC-UST): a multilabel dataset from an urban acoustic sensor network](https://zenodo.org/records/3966543) +- [VocalSound: A Dataset for Improving Human Vocal Sounds Recognition](https://arxiv.org/abs/2205.03433) +- [WavText5K](https://github.com/microsoft/WavText5K) +- [CSS10: A Collection of Single Speaker Speech Datasets for 10 Languages](https://github.com/Kyubyong/css10) +- [Hi-Fi Multi-Speaker English TTS Dataset (Hi-Fi TTS)](https://www.openslr.org/109/) +- [IIIT-H Indic Speech Databases](http://festvox.org/databases/iiit_voices/) +- [Libri-Light: A Benchmark for ASR with Limited or No Supervision](https://arxiv.org/abs/1912.07875) +- [LibriTTS: A Corpus Derived from LibriSpeech for Text-to-Speech](https://www.openslr.org/60) +- [LibriTTS-R: A Restored Multi-Speaker Text-to-Speech Corpus](https://www.openslr.org/141/) +- [The SIWIS French Speech Synthesis Database](https://datashare.ed.ac.uk/handle/10283/2353) +- [Crowdsourced high-quality Colombian Spanish speech data set](https://openslr.org/72/) +- [TTS-Portuguese Corpus](https://github.com/Edresson/TTS-Portuguese-Corpus) +- [CSTR VCTK Corpus: English Multi-speaker Corpus for CSTR Voice Cloning Toolkit](https://datashare.ed.ac.uk/handle/10283/3443) + +\*\* Data Collection Method by dataset
+ +- Human
+ +\*\* Labeling Method by dataset (for those with labels)
+ +- Hybrid: Automated, Human, Unknown
+ +### Evaluating Dataset: + +Properties: The audio generation quality of BigVGAN is evaluated using `dev` splits of the [LibriTTS dataset](https://www.openslr.org/60/) and [Hi-Fi TTS dataset](https://www.openslr.org/109/). The datasets include speech in English language with equal balance of genders. + +\*\* Data Collection Method by dataset
+ +- Human
+ +\*\* Labeling Method by dataset
+ +- Automated
+ +## Inference: + +**Engine:** PyTorch
+**Test Hardware:** NVIDIA A100 GPU
+ +## Ethical Considerations: + +NVIDIA believes Trustworthy AI is a shared responsibility and we have established policies and practices to enable development for a wide array of AI applications. When downloaded or used in accordance with our terms of service, developers should work with their internal model team to ensure this model meets requirements for the relevant industry and use case and addresses unforeseen product misuse. For more detailed information on ethical considerations for this model, please see the Model Card++ Explainability, Bias, Safety & Security, and Privacy Subcards. Please report security vulnerabilities or NVIDIA AI Concerns [here](https://www.nvidia.com/en-us/support/submit-security-vulnerability/). diff --git a/src/third_party/BigVGAN/nv-modelcard++/privacy.md b/src/third_party/BigVGAN/nv-modelcard++/privacy.md new file mode 100644 index 0000000000000000000000000000000000000000..6343648a81614a03910f3ac17f529b6e25b423f4 --- /dev/null +++ b/src/third_party/BigVGAN/nv-modelcard++/privacy.md @@ -0,0 +1,14 @@ +| Field | Response | +| :------------------------------------------------------------------------------------------------------------------------------------- | :--------------------------------------------- | +| Generatable or reverse engineerable personal information? | None | +| Protected class data used to create this model? | None | +| Was consent obtained for any personal data used? | Not Applicable (No Personal Data) | +| How often is dataset reviewed? | Before Release | +| Is a mechanism in place to honor data subject right of access or deletion of personal data? | Not Applicable | +| If personal collected for the development of the model, was it collected directly by NVIDIA? | Not Applicable | +| If personal collected for the development of the model by NVIDIA, do you maintain or have access to disclosures made to data subjects? | Not Applicable | +| If personal collected for the development of this AI model, was it minimized to only what was required? | Not Applicable | +| Is data in dataset traceable? | Yes | +| Is there provenance for all datasets used in training? | Yes | +| Does data labeling (annotation, metadata) comply with privacy laws? | Yes | +| Is data compliant with data subject requests for data correction or removal, if such a request was made? | No, not possible with externally-sourced data. | diff --git a/src/third_party/BigVGAN/nv-modelcard++/safety.md b/src/third_party/BigVGAN/nv-modelcard++/safety.md new file mode 100644 index 0000000000000000000000000000000000000000..42c3d57eecaa907d04beaccf5dff9d906b61e2a5 --- /dev/null +++ b/src/third_party/BigVGAN/nv-modelcard++/safety.md @@ -0,0 +1,6 @@ +| Field | Response | +| :---------------------------------------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Model Application(s): | Synethic Audio Generation | +| Describe the life critical impact (if present). | Not Applicable | +| Use Case Restrictions: | None | +| Model and dataset restrictions: | The Principle of least privilege (PoLP) is applied limiting access for dataset generation and model development. Restrictions enforce dataset access during training, and dataset license constraints adhered to. | diff --git a/src/third_party/BigVGAN/requirements.txt b/src/third_party/BigVGAN/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..ba5e7d899616f68a753cfc0f7c4ab43c3c9e9a3a --- /dev/null +++ b/src/third_party/BigVGAN/requirements.txt @@ -0,0 +1,13 @@ +torch +numpy +librosa>=0.8.1 +scipy +tensorboard +soundfile +matplotlib +pesq +auraloss +tqdm +nnAudio +ninja +huggingface_hub>=0.23.4 \ No newline at end of file diff --git a/src/third_party/BigVGAN/tests/test_activation.py b/src/third_party/BigVGAN/tests/test_activation.py new file mode 100644 index 0000000000000000000000000000000000000000..fb7d35cb605510e4a9a6227f6c6993e82e27ef6c --- /dev/null +++ b/src/third_party/BigVGAN/tests/test_activation.py @@ -0,0 +1,65 @@ +# Copyright (c) 2024 NVIDIA CORPORATION. +# Licensed under the MIT license. + +import os +import sys +# to import modules from parent_dir +parent_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), "..")) +sys.path.append(parent_dir) + +import torch +from alias_free_activation.cuda import activation1d +from activations import Snake + + +def test_load_fused_kernels(): + try: + print("[Success] load_fused_kernels") + except ImportError as e: + print("[Fail] load_fused_kernels") + raise e + + +def test_anti_alias_activation(): + data = torch.rand((10, 10, 200), device="cuda") + + # Check activations.Snake cuda vs. torch + fused_anti_alias_activation = activation1d.Activation1d( + activation=Snake(10), fused=True + ).cuda() + fused_activation_output = fused_anti_alias_activation(data) + + torch_anti_alias_activation = activation1d.Activation1d( + activation=Snake(10), fused=False + ).cuda() + torch_activation_output = torch_anti_alias_activation(data) + + test_result = (fused_activation_output - torch_activation_output).abs() + + while test_result.dim() != 1: + test_result = test_result.mean(dim=-1) + + diff = test_result.mean(dim=-1) + + if diff <= 1e-3: + print( + f"\n[Success] test_fused_anti_alias_activation" + f"\n > mean_difference={diff}" + f"\n > fused_values={fused_activation_output[-1][-1][:].tolist()}" + f"\n > torch_values={torch_activation_output[-1][-1][:].tolist()}" + ) + else: + print( + f"\n[Fail] test_fused_anti_alias_activation" + f"\n > mean_difference={diff}, " + f"\n > fused_values={fused_activation_output[-1][-1][:].tolist()}, " + f"\n > torch_values={torch_activation_output[-1][-1][:].tolist()}" + ) + + +if __name__ == "__main__": + from alias_free_activation.cuda import load + + load.load() + test_load_fused_kernels() + test_anti_alias_activation() diff --git a/src/third_party/BigVGAN/tests/test_activation_snake_beta.py b/src/third_party/BigVGAN/tests/test_activation_snake_beta.py new file mode 100644 index 0000000000000000000000000000000000000000..1f9aea899e2564e47c62b8b22dca4e581da64f54 --- /dev/null +++ b/src/third_party/BigVGAN/tests/test_activation_snake_beta.py @@ -0,0 +1,66 @@ +# Copyright (c) 2024 NVIDIA CORPORATION. +# Licensed under the MIT license. + +import os +import sys +# to import modules from parent_dir +parent_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), "..")) +sys.path.append(parent_dir) + +import torch +from alias_free_activation.cuda import activation1d +from activations import SnakeBeta + + +def test_load_fused_kernels(): + try: + print("[Success] load_fused_kernels") + except ImportError as e: + print("[Fail] load_fused_kernels") + raise e + + +def test_anti_alias_activation(): + data = torch.rand((10, 10, 200), device="cuda") + + # Check activations, Snake CUDA vs. Torch + fused_anti_alias_activation = activation1d.Activation1d( + activation=SnakeBeta(10), fused=True + ).cuda() + fused_activation_output = fused_anti_alias_activation(data) + + torch_anti_alias_activation = activation1d.Activation1d( + activation=SnakeBeta(10), fused=False + ).cuda() + torch_activation_output = torch_anti_alias_activation(data) + + test_result = (fused_activation_output - torch_activation_output).abs() + + while test_result.dim() != 1: + test_result = test_result.mean(dim=-1) + + diff = test_result.mean(dim=-1) + + if diff <= 1e-3: + print( + f"\n[Success] test_fused_anti_alias_activation" + f"\n > mean_difference={diff}" + f"\n > fused_values={fused_activation_output[-1][-1][:].tolist()}" + f"\n > torch_values={torch_activation_output[-1][-1][:].tolist()}" + ) + else: + print( + f"\n[Fail] test_fused_anti_alias_activation" + f"\n > mean_difference={diff}, " + f"\n > fused_values={fused_activation_output[-1][-1][:].tolist()}, " + f"\n > torch_values={torch_activation_output[-1][-1][:].tolist()}" + ) + + + +if __name__ == "__main__": + from alias_free_activation.cuda import load + + load.load() + test_load_fused_kernels() + test_anti_alias_activation() diff --git a/src/third_party/BigVGAN/tests/test_cuda_vs_torch_model.py b/src/third_party/BigVGAN/tests/test_cuda_vs_torch_model.py new file mode 100644 index 0000000000000000000000000000000000000000..fda08a19bab4170b0b3eea3e9a32815389ab9263 --- /dev/null +++ b/src/third_party/BigVGAN/tests/test_cuda_vs_torch_model.py @@ -0,0 +1,221 @@ +# Copyright (c) 2024 NVIDIA CORPORATION. +# Licensed under the MIT license. + +import os +import sys + +# to import modules from parent_dir +parent_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), "..")) +sys.path.append(parent_dir) + +import torch +import json +from env import AttrDict +from bigvgan import BigVGAN +from time import time +from tqdm import tqdm +from meldataset import mel_spectrogram, MAX_WAV_VALUE +from scipy.io.wavfile import write +import numpy as np + +import argparse + +torch.backends.cudnn.benchmark = True + +# For easier debugging +torch.set_printoptions(linewidth=200, threshold=10_000) + + +def generate_soundwave(duration=5.0, sr=24000): + t = np.linspace(0, duration, int(sr * duration), False, dtype=np.float32) + + modulation = np.sin(2 * np.pi * t / duration) + + min_freq = 220 + max_freq = 1760 + frequencies = min_freq + (max_freq - min_freq) * (modulation + 1) / 2 + soundwave = np.sin(2 * np.pi * frequencies * t) + + soundwave = soundwave / np.max(np.abs(soundwave)) * 0.95 + + return soundwave, sr + + +def get_mel(x, h): + return mel_spectrogram( + x, h.n_fft, h.num_mels, h.sampling_rate, h.hop_size, h.win_size, h.fmin, h.fmax + ) + + +def load_checkpoint(filepath, device): + assert os.path.isfile(filepath) + print(f"Loading '{filepath}'") + checkpoint_dict = torch.load(filepath, map_location=device) + print("Complete.") + return checkpoint_dict + + +if __name__ == "__main__": + parser = argparse.ArgumentParser( + description="Test script to check CUDA kernel correctness." + ) + parser.add_argument( + "--checkpoint_file", + type=str, + required=True, + help="Path to the checkpoint file. Assumes config.json exists in the directory.", + ) + + args = parser.parse_args() + + config_file = os.path.join(os.path.split(args.checkpoint_file)[0], "config.json") + with open(config_file) as f: + config = f.read() + json_config = json.loads(config) + h = AttrDict({**json_config}) + + print("loading plain Pytorch BigVGAN") + generator_original = BigVGAN(h).to("cuda") + print("loading CUDA kernel BigVGAN with auto-build") + generator_cuda_kernel = BigVGAN(h, use_cuda_kernel=True).to("cuda") + + state_dict_g = load_checkpoint(args.checkpoint_file, "cuda") + generator_original.load_state_dict(state_dict_g["generator"]) + generator_cuda_kernel.load_state_dict(state_dict_g["generator"]) + + generator_original.remove_weight_norm() + generator_original.eval() + generator_cuda_kernel.remove_weight_norm() + generator_cuda_kernel.eval() + + # define number of samples and length of mel frame to benchmark + num_sample = 10 + num_mel_frame = 16384 + + # CUDA kernel correctness check + diff = 0.0 + for i in tqdm(range(num_sample)): + # Random mel + data = torch.rand((1, h.num_mels, num_mel_frame), device="cuda") + + with torch.inference_mode(): + audio_original = generator_original(data) + + with torch.inference_mode(): + audio_cuda_kernel = generator_cuda_kernel(data) + + # Both outputs should be (almost) the same + test_result = (audio_original - audio_cuda_kernel).abs() + diff += test_result.mean(dim=-1).item() + + diff /= num_sample + if ( + diff <= 2e-3 + ): # We can expect a small difference (~1e-3) which does not affect perceptual quality + print( + f"\n[Success] test CUDA fused vs. plain torch BigVGAN inference" + f"\n > mean_difference={diff}" + f"\n > fused_values={audio_cuda_kernel[-1][-1][-30:].tolist()}" + f"\n > torch_values={audio_original[-1][-1][-30:].tolist()}" + ) + else: + print( + f"\n[Fail] test CUDA fused vs. plain torch BigVGAN inference" + f"\n > mean_difference={diff}" + f"\n > fused_values={audio_cuda_kernel[-1][-1][-30:].tolist()}, " + f"\n > torch_values={audio_original[-1][-1][-30:].tolist()}" + ) + + del data, audio_original, audio_cuda_kernel + + # Variables for tracking total time and VRAM usage + toc_total_original = 0 + toc_total_cuda_kernel = 0 + vram_used_original_total = 0 + vram_used_cuda_kernel_total = 0 + audio_length_total = 0 + + # Measure Original inference in isolation + for i in tqdm(range(num_sample)): + torch.cuda.reset_peak_memory_stats(device="cuda") + data = torch.rand((1, h.num_mels, num_mel_frame), device="cuda") + torch.cuda.synchronize() + tic = time() + with torch.inference_mode(): + audio_original = generator_original(data) + torch.cuda.synchronize() + toc = time() - tic + toc_total_original += toc + + vram_used_original_total += torch.cuda.max_memory_allocated(device="cuda") + + del data, audio_original + torch.cuda.empty_cache() + + # Measure CUDA kernel inference in isolation + for i in tqdm(range(num_sample)): + torch.cuda.reset_peak_memory_stats(device="cuda") + data = torch.rand((1, h.num_mels, num_mel_frame), device="cuda") + torch.cuda.synchronize() + tic = time() + with torch.inference_mode(): + audio_cuda_kernel = generator_cuda_kernel(data) + torch.cuda.synchronize() + toc = time() - tic + toc_total_cuda_kernel += toc + + audio_length_total += audio_cuda_kernel.shape[-1] + + vram_used_cuda_kernel_total += torch.cuda.max_memory_allocated(device="cuda") + + del data, audio_cuda_kernel + torch.cuda.empty_cache() + + # Calculate metrics + audio_second = audio_length_total / h.sampling_rate + khz_original = audio_length_total / toc_total_original / 1000 + khz_cuda_kernel = audio_length_total / toc_total_cuda_kernel / 1000 + vram_used_original_gb = vram_used_original_total / num_sample / (1024 ** 3) + vram_used_cuda_kernel_gb = vram_used_cuda_kernel_total / num_sample / (1024 ** 3) + + # Print results + print( + f"Original BigVGAN: took {toc_total_original:.2f} seconds to generate {audio_second:.2f} seconds of audio, {khz_original:.1f}kHz, {audio_second / toc_total_original:.1f} faster than realtime, VRAM used {vram_used_original_gb:.1f} GB" + ) + print( + f"CUDA kernel BigVGAN: took {toc_total_cuda_kernel:.2f} seconds to generate {audio_second:.2f} seconds of audio, {khz_cuda_kernel:.1f}kHz, {audio_second / toc_total_cuda_kernel:.1f} faster than realtime, VRAM used {vram_used_cuda_kernel_gb:.1f} GB" + ) + print(f"speedup of CUDA kernel: {khz_cuda_kernel / khz_original}") + print(f"VRAM saving of CUDA kernel: {vram_used_original_gb / vram_used_cuda_kernel_gb}") + + # Use artificial sine waves for inference test + audio_real, sr = generate_soundwave(duration=5.0, sr=h.sampling_rate) + audio_real = torch.tensor(audio_real).to("cuda") + # Compute mel spectrogram from the ground truth audio + x = get_mel(audio_real.unsqueeze(0), h) + + with torch.inference_mode(): + y_g_hat_original = generator_original(x) + y_g_hat_cuda_kernel = generator_cuda_kernel(x) + + audio_real = audio_real.squeeze() + audio_real = audio_real * MAX_WAV_VALUE + audio_real = audio_real.cpu().numpy().astype("int16") + + audio_original = y_g_hat_original.squeeze() + audio_original = audio_original * MAX_WAV_VALUE + audio_original = audio_original.cpu().numpy().astype("int16") + + audio_cuda_kernel = y_g_hat_cuda_kernel.squeeze() + audio_cuda_kernel = audio_cuda_kernel * MAX_WAV_VALUE + audio_cuda_kernel = audio_cuda_kernel.cpu().numpy().astype("int16") + + os.makedirs("tmp", exist_ok=True) + output_file_real = os.path.join("tmp", "audio_real.wav") + output_file_original = os.path.join("tmp", "audio_generated_original.wav") + output_file_cuda_kernel = os.path.join("tmp", "audio_generated_cuda_kernel.wav") + write(output_file_real, h.sampling_rate, audio_real) + write(output_file_original, h.sampling_rate, audio_original) + write(output_file_cuda_kernel, h.sampling_rate, audio_cuda_kernel) + print("Example generated audios of original vs. fused CUDA kernel written to tmp!") + print("Done") diff --git a/src/third_party/BigVGAN/train.py b/src/third_party/BigVGAN/train.py new file mode 100644 index 0000000000000000000000000000000000000000..c0745272189ed443b6c7421d21e44d6576c71f88 --- /dev/null +++ b/src/third_party/BigVGAN/train.py @@ -0,0 +1,777 @@ +# Copyright (c) 2024 NVIDIA CORPORATION. +# Licensed under the MIT license. + +# Adapted from https://github.com/jik876/hifi-gan under the MIT license. +# LICENSE is in incl_licenses directory. + + +import warnings + +warnings.simplefilter(action="ignore", category=FutureWarning) +import itertools +import os +import time +import argparse +import json +import torch +import torch.nn.functional as F +from torch.utils.tensorboard import SummaryWriter +from torch.utils.data import DistributedSampler, DataLoader +import torch.multiprocessing as mp +from torch.distributed import init_process_group +from torch.nn.parallel import DistributedDataParallel +from env import AttrDict, build_env +from meldataset import MelDataset, mel_spectrogram, get_dataset_filelist, MAX_WAV_VALUE + +from bigvgan import BigVGAN +from discriminators import ( + MultiPeriodDiscriminator, + MultiResolutionDiscriminator, + MultiBandDiscriminator, + MultiScaleSubbandCQTDiscriminator, +) +from loss import ( + feature_loss, + generator_loss, + discriminator_loss, + MultiScaleMelSpectrogramLoss, +) + +from utils import ( + plot_spectrogram, + plot_spectrogram_clipped, + scan_checkpoint, + load_checkpoint, + save_checkpoint, + save_audio, +) +import torchaudio as ta +from pesq import pesq +from tqdm import tqdm +import auraloss + +torch.backends.cudnn.benchmark = False + + +def train(rank, a, h): + if h.num_gpus > 1: + # initialize distributed + init_process_group( + backend=h.dist_config["dist_backend"], + init_method=h.dist_config["dist_url"], + world_size=h.dist_config["world_size"] * h.num_gpus, + rank=rank, + ) + + # Set seed and device + torch.cuda.manual_seed(h.seed) + torch.cuda.set_device(rank) + device = torch.device(f"cuda:{rank:d}") + + # Define BigVGAN generator + generator = BigVGAN(h).to(device) + + # Define discriminators. MPD is used by default + mpd = MultiPeriodDiscriminator(h).to(device) + + # Define additional discriminators. BigVGAN-v1 uses UnivNet's MRD as default + # New in BigVGAN-v2: option to switch to new discriminators: MultiBandDiscriminator / MultiScaleSubbandCQTDiscriminator + if h.get("use_mbd_instead_of_mrd", False): # Switch to MBD + print( + "[INFO] using MultiBandDiscriminator of BigVGAN-v2 instead of MultiResolutionDiscriminator" + ) + # Variable name is kept as "mrd" for backward compatibility & minimal code change + mrd = MultiBandDiscriminator(h).to(device) + elif h.get("use_cqtd_instead_of_mrd", False): # Switch to CQTD + print( + "[INFO] using MultiScaleSubbandCQTDiscriminator of BigVGAN-v2 instead of MultiResolutionDiscriminator" + ) + mrd = MultiScaleSubbandCQTDiscriminator(h).to(device) + else: # Fallback to original MRD in BigVGAN-v1 + mrd = MultiResolutionDiscriminator(h).to(device) + + # New in BigVGAN-v2: option to switch to multi-scale L1 mel loss + if h.get("use_multiscale_melloss", False): + print( + "[INFO] using multi-scale Mel l1 loss of BigVGAN-v2 instead of the original single-scale loss" + ) + fn_mel_loss_multiscale = MultiScaleMelSpectrogramLoss( + sampling_rate=h.sampling_rate + ) # NOTE: accepts waveform as input + else: + fn_mel_loss_singlescale = F.l1_loss + + # Print the model & number of parameters, and create or scan the latest checkpoint from checkpoints directory + if rank == 0: + print(generator) + print(mpd) + print(mrd) + print(f"Generator params: {sum(p.numel() for p in generator.parameters())}") + print(f"Discriminator mpd params: {sum(p.numel() for p in mpd.parameters())}") + print(f"Discriminator mrd params: {sum(p.numel() for p in mrd.parameters())}") + os.makedirs(a.checkpoint_path, exist_ok=True) + print(f"Checkpoints directory: {a.checkpoint_path}") + + if os.path.isdir(a.checkpoint_path): + # New in v2.1: If the step prefix pattern-based checkpoints are not found, also check for renamed files in Hugging Face Hub to resume training + cp_g = scan_checkpoint( + a.checkpoint_path, prefix="g_", renamed_file="bigvgan_generator.pt" + ) + cp_do = scan_checkpoint( + a.checkpoint_path, + prefix="do_", + renamed_file="bigvgan_discriminator_optimizer.pt", + ) + + # Load the latest checkpoint if exists + steps = 0 + if cp_g is None or cp_do is None: + state_dict_do = None + last_epoch = -1 + else: + state_dict_g = load_checkpoint(cp_g, device) + state_dict_do = load_checkpoint(cp_do, device) + generator.load_state_dict(state_dict_g["generator"]) + mpd.load_state_dict(state_dict_do["mpd"]) + mrd.load_state_dict(state_dict_do["mrd"]) + steps = state_dict_do["steps"] + 1 + last_epoch = state_dict_do["epoch"] + + # Initialize DDP, optimizers, and schedulers + if h.num_gpus > 1: + generator = DistributedDataParallel(generator, device_ids=[rank]).to(device) + mpd = DistributedDataParallel(mpd, device_ids=[rank]).to(device) + mrd = DistributedDataParallel(mrd, device_ids=[rank]).to(device) + + optim_g = torch.optim.AdamW( + generator.parameters(), h.learning_rate, betas=[h.adam_b1, h.adam_b2] + ) + optim_d = torch.optim.AdamW( + itertools.chain(mrd.parameters(), mpd.parameters()), + h.learning_rate, + betas=[h.adam_b1, h.adam_b2], + ) + + if state_dict_do is not None: + optim_g.load_state_dict(state_dict_do["optim_g"]) + optim_d.load_state_dict(state_dict_do["optim_d"]) + + scheduler_g = torch.optim.lr_scheduler.ExponentialLR( + optim_g, gamma=h.lr_decay, last_epoch=last_epoch + ) + scheduler_d = torch.optim.lr_scheduler.ExponentialLR( + optim_d, gamma=h.lr_decay, last_epoch=last_epoch + ) + + # Define training and validation datasets + + """ + unseen_validation_filelist will contain sample filepaths outside the seen training & validation dataset + Example: trained on LibriTTS, validate on VCTK + """ + training_filelist, validation_filelist, list_unseen_validation_filelist = ( + get_dataset_filelist(a) + ) + + trainset = MelDataset( + training_filelist, + h, + h.segment_size, + h.n_fft, + h.num_mels, + h.hop_size, + h.win_size, + h.sampling_rate, + h.fmin, + h.fmax, + shuffle=False if h.num_gpus > 1 else True, + fmax_loss=h.fmax_for_loss, + device=device, + fine_tuning=a.fine_tuning, + base_mels_path=a.input_mels_dir, + is_seen=True, + ) + + train_sampler = DistributedSampler(trainset) if h.num_gpus > 1 else None + + train_loader = DataLoader( + trainset, + num_workers=h.num_workers, + shuffle=False, + sampler=train_sampler, + batch_size=h.batch_size, + pin_memory=True, + drop_last=True, + ) + + if rank == 0: + validset = MelDataset( + validation_filelist, + h, + h.segment_size, + h.n_fft, + h.num_mels, + h.hop_size, + h.win_size, + h.sampling_rate, + h.fmin, + h.fmax, + False, + False, + fmax_loss=h.fmax_for_loss, + device=device, + fine_tuning=a.fine_tuning, + base_mels_path=a.input_mels_dir, + is_seen=True, + ) + validation_loader = DataLoader( + validset, + num_workers=1, + shuffle=False, + sampler=None, + batch_size=1, + pin_memory=True, + drop_last=True, + ) + + list_unseen_validset = [] + list_unseen_validation_loader = [] + for i in range(len(list_unseen_validation_filelist)): + unseen_validset = MelDataset( + list_unseen_validation_filelist[i], + h, + h.segment_size, + h.n_fft, + h.num_mels, + h.hop_size, + h.win_size, + h.sampling_rate, + h.fmin, + h.fmax, + False, + False, + fmax_loss=h.fmax_for_loss, + device=device, + fine_tuning=a.fine_tuning, + base_mels_path=a.input_mels_dir, + is_seen=False, + ) + unseen_validation_loader = DataLoader( + unseen_validset, + num_workers=1, + shuffle=False, + sampler=None, + batch_size=1, + pin_memory=True, + drop_last=True, + ) + list_unseen_validset.append(unseen_validset) + list_unseen_validation_loader.append(unseen_validation_loader) + + # Tensorboard logger + sw = SummaryWriter(os.path.join(a.checkpoint_path, "logs")) + if a.save_audio: # Also save audio to disk if --save_audio is set to True + os.makedirs(os.path.join(a.checkpoint_path, "samples"), exist_ok=True) + + """ + Validation loop, "mode" parameter is automatically defined as (seen or unseen)_(name of the dataset). + If the name of the dataset contains "nonspeech", it skips PESQ calculation to prevent errors + """ + + def validate(rank, a, h, loader, mode="seen"): + assert rank == 0, "validate should only run on rank=0" + generator.eval() + torch.cuda.empty_cache() + + val_err_tot = 0 + val_pesq_tot = 0 + val_mrstft_tot = 0 + + # Modules for evaluation metrics + pesq_resampler = ta.transforms.Resample(h.sampling_rate, 16000).cuda() + loss_mrstft = auraloss.freq.MultiResolutionSTFTLoss(device="cuda") + + if a.save_audio: # Also save audio to disk if --save_audio is set to True + os.makedirs( + os.path.join(a.checkpoint_path, "samples", f"gt_{mode}"), + exist_ok=True, + ) + os.makedirs( + os.path.join(a.checkpoint_path, "samples", f"{mode}_{steps:08d}"), + exist_ok=True, + ) + + with torch.no_grad(): + print(f"step {steps} {mode} speaker validation...") + + # Loop over validation set and compute metrics + for j, batch in enumerate(tqdm(loader)): + x, y, _, y_mel = batch + y = y.to(device) + if hasattr(generator, "module"): + y_g_hat = generator.module(x.to(device)) + else: + y_g_hat = generator(x.to(device)) + y_mel = y_mel.to(device, non_blocking=True) + y_g_hat_mel = mel_spectrogram( + y_g_hat.squeeze(1), + h.n_fft, + h.num_mels, + h.sampling_rate, + h.hop_size, + h.win_size, + h.fmin, + h.fmax_for_loss, + ) + min_t = min(y_mel.size(-1), y_g_hat_mel.size(-1)) + val_err_tot += F.l1_loss(y_mel[...,:min_t], y_g_hat_mel[...,:min_t]).item() + + # PESQ calculation. only evaluate PESQ if it's speech signal (nonspeech PESQ will error out) + if ( + not "nonspeech" in mode + ): # Skips if the name of dataset (in mode string) contains "nonspeech" + + # Resample to 16000 for pesq + y_16k = pesq_resampler(y) + y_g_hat_16k = pesq_resampler(y_g_hat.squeeze(1)) + y_int_16k = (y_16k[0] * MAX_WAV_VALUE).short().cpu().numpy() + y_g_hat_int_16k = ( + (y_g_hat_16k[0] * MAX_WAV_VALUE).short().cpu().numpy() + ) + val_pesq_tot += pesq(16000, y_int_16k, y_g_hat_int_16k, "wb") + + # MRSTFT calculation + min_t = min(y.size(-1), y_g_hat.size(-1)) + val_mrstft_tot += loss_mrstft(y_g_hat[...,:min_t], y[...,:min_t]).item() + + # Log audio and figures to Tensorboard + if j % a.eval_subsample == 0: # Subsample every nth from validation set + if steps >= 0: + sw.add_audio(f"gt_{mode}/y_{j}", y[0], steps, h.sampling_rate) + if ( + a.save_audio + ): # Also save audio to disk if --save_audio is set to True + save_audio( + y[0], + os.path.join( + a.checkpoint_path, + "samples", + f"gt_{mode}", + f"{j:04d}.wav", + ), + h.sampling_rate, + ) + sw.add_figure( + f"gt_{mode}/y_spec_{j}", + plot_spectrogram(x[0]), + steps, + ) + + sw.add_audio( + f"generated_{mode}/y_hat_{j}", + y_g_hat[0], + steps, + h.sampling_rate, + ) + if ( + a.save_audio + ): # Also save audio to disk if --save_audio is set to True + save_audio( + y_g_hat[0, 0], + os.path.join( + a.checkpoint_path, + "samples", + f"{mode}_{steps:08d}", + f"{j:04d}.wav", + ), + h.sampling_rate, + ) + # Spectrogram of synthesized audio + y_hat_spec = mel_spectrogram( + y_g_hat.squeeze(1), + h.n_fft, + h.num_mels, + h.sampling_rate, + h.hop_size, + h.win_size, + h.fmin, + h.fmax, + ) + sw.add_figure( + f"generated_{mode}/y_hat_spec_{j}", + plot_spectrogram(y_hat_spec.squeeze(0).cpu().numpy()), + steps, + ) + + """ + Visualization of spectrogram difference between GT and synthesized audio, difference higher than 1 is clipped for better visualization. + """ + spec_delta = torch.clamp( + torch.abs(x[0] - y_hat_spec.squeeze(0).cpu()), + min=1e-6, + max=1.0, + ) + sw.add_figure( + f"delta_dclip1_{mode}/spec_{j}", + plot_spectrogram_clipped(spec_delta.numpy(), clip_max=1.0), + steps, + ) + + val_err = val_err_tot / (j + 1) + val_pesq = val_pesq_tot / (j + 1) + val_mrstft = val_mrstft_tot / (j + 1) + # Log evaluation metrics to Tensorboard + sw.add_scalar(f"validation_{mode}/mel_spec_error", val_err, steps) + sw.add_scalar(f"validation_{mode}/pesq", val_pesq, steps) + sw.add_scalar(f"validation_{mode}/mrstft", val_mrstft, steps) + + generator.train() + + # If the checkpoint is loaded, start with validation loop + if steps != 0 and rank == 0 and not a.debug: + if not a.skip_seen: + validate( + rank, + a, + h, + validation_loader, + mode=f"seen_{train_loader.dataset.name}", + ) + for i in range(len(list_unseen_validation_loader)): + validate( + rank, + a, + h, + list_unseen_validation_loader[i], + mode=f"unseen_{list_unseen_validation_loader[i].dataset.name}", + ) + # Exit the script if --evaluate is set to True + if a.evaluate: + exit() + + # Main training loop + generator.train() + mpd.train() + mrd.train() + for epoch in range(max(0, last_epoch), a.training_epochs): + if rank == 0: + start = time.time() + print(f"Epoch: {epoch + 1}") + + if h.num_gpus > 1: + train_sampler.set_epoch(epoch) + + for i, batch in enumerate(train_loader): + if rank == 0: + start_b = time.time() + x, y, _, y_mel = batch + + x = x.to(device, non_blocking=True) + y = y.to(device, non_blocking=True) + y_mel = y_mel.to(device, non_blocking=True) + y = y.unsqueeze(1) + + y_g_hat = generator(x) + y_g_hat_mel = mel_spectrogram( + y_g_hat.squeeze(1), + h.n_fft, + h.num_mels, + h.sampling_rate, + h.hop_size, + h.win_size, + h.fmin, + h.fmax_for_loss, + ) + + optim_d.zero_grad() + + # MPD + y_df_hat_r, y_df_hat_g, _, _ = mpd(y, y_g_hat.detach()) + loss_disc_f, losses_disc_f_r, losses_disc_f_g = discriminator_loss( + y_df_hat_r, y_df_hat_g + ) + + # MRD + y_ds_hat_r, y_ds_hat_g, _, _ = mrd(y, y_g_hat.detach()) + loss_disc_s, losses_disc_s_r, losses_disc_s_g = discriminator_loss( + y_ds_hat_r, y_ds_hat_g + ) + + loss_disc_all = loss_disc_s + loss_disc_f + + # Set clip_grad_norm value + clip_grad_norm = h.get("clip_grad_norm", 1000.0) # Default to 1000 + + # Whether to freeze D for initial training steps + if steps >= a.freeze_step: + loss_disc_all.backward() + grad_norm_mpd = torch.nn.utils.clip_grad_norm_( + mpd.parameters(), clip_grad_norm + ) + grad_norm_mrd = torch.nn.utils.clip_grad_norm_( + mrd.parameters(), clip_grad_norm + ) + optim_d.step() + else: + print( + f"[WARNING] skipping D training for the first {a.freeze_step} steps" + ) + grad_norm_mpd = 0.0 + grad_norm_mrd = 0.0 + + # Generator + optim_g.zero_grad() + + # L1 Mel-Spectrogram Loss + lambda_melloss = h.get( + "lambda_melloss", 45.0 + ) # Defaults to 45 in BigVGAN-v1 if not set + if h.get("use_multiscale_melloss", False): # uses wav for loss + loss_mel = fn_mel_loss_multiscale(y, y_g_hat) * lambda_melloss + else: # Uses mel for loss + loss_mel = fn_mel_loss_singlescale(y_mel, y_g_hat_mel) * lambda_melloss + + # MPD loss + y_df_hat_r, y_df_hat_g, fmap_f_r, fmap_f_g = mpd(y, y_g_hat) + loss_fm_f = feature_loss(fmap_f_r, fmap_f_g) + loss_gen_f, losses_gen_f = generator_loss(y_df_hat_g) + + # MRD loss + y_ds_hat_r, y_ds_hat_g, fmap_s_r, fmap_s_g = mrd(y, y_g_hat) + loss_fm_s = feature_loss(fmap_s_r, fmap_s_g) + loss_gen_s, losses_gen_s = generator_loss(y_ds_hat_g) + + if steps >= a.freeze_step: + loss_gen_all = ( + loss_gen_s + loss_gen_f + loss_fm_s + loss_fm_f + loss_mel + ) + else: + print( + f"[WARNING] using regression loss only for G for the first {a.freeze_step} steps" + ) + loss_gen_all = loss_mel + + loss_gen_all.backward() + grad_norm_g = torch.nn.utils.clip_grad_norm_( + generator.parameters(), clip_grad_norm + ) + optim_g.step() + + if rank == 0: + # STDOUT logging + if steps % a.stdout_interval == 0: + mel_error = ( + loss_mel.item() / lambda_melloss + ) # Log training mel regression loss to stdout + print( + f"Steps: {steps:d}, " + f"Gen Loss Total: {loss_gen_all:4.3f}, " + f"Mel Error: {mel_error:4.3f}, " + f"s/b: {time.time() - start_b:4.3f} " + f"lr: {optim_g.param_groups[0]['lr']:4.7f} " + f"grad_norm_g: {grad_norm_g:4.3f}" + ) + + # Checkpointing + if steps % a.checkpoint_interval == 0 and steps != 0: + checkpoint_path = f"{a.checkpoint_path}/g_{steps:08d}" + save_checkpoint( + checkpoint_path, + { + "generator": ( + generator.module if h.num_gpus > 1 else generator + ).state_dict() + }, + ) + checkpoint_path = f"{a.checkpoint_path}/do_{steps:08d}" + save_checkpoint( + checkpoint_path, + { + "mpd": (mpd.module if h.num_gpus > 1 else mpd).state_dict(), + "mrd": (mrd.module if h.num_gpus > 1 else mrd).state_dict(), + "optim_g": optim_g.state_dict(), + "optim_d": optim_d.state_dict(), + "steps": steps, + "epoch": epoch, + }, + ) + + # Tensorboard summary logging + if steps % a.summary_interval == 0: + mel_error = ( + loss_mel.item() / lambda_melloss + ) # Log training mel regression loss to tensorboard + sw.add_scalar("training/gen_loss_total", loss_gen_all.item(), steps) + sw.add_scalar("training/mel_spec_error", mel_error, steps) + sw.add_scalar("training/fm_loss_mpd", loss_fm_f.item(), steps) + sw.add_scalar("training/gen_loss_mpd", loss_gen_f.item(), steps) + sw.add_scalar("training/disc_loss_mpd", loss_disc_f.item(), steps) + sw.add_scalar("training/grad_norm_mpd", grad_norm_mpd, steps) + sw.add_scalar("training/fm_loss_mrd", loss_fm_s.item(), steps) + sw.add_scalar("training/gen_loss_mrd", loss_gen_s.item(), steps) + sw.add_scalar("training/disc_loss_mrd", loss_disc_s.item(), steps) + sw.add_scalar("training/grad_norm_mrd", grad_norm_mrd, steps) + sw.add_scalar("training/grad_norm_g", grad_norm_g, steps) + sw.add_scalar( + "training/learning_rate_d", scheduler_d.get_last_lr()[0], steps + ) + sw.add_scalar( + "training/learning_rate_g", scheduler_g.get_last_lr()[0], steps + ) + sw.add_scalar("training/epoch", epoch + 1, steps) + + # Validation + if steps % a.validation_interval == 0: + # Plot training input x so far used + for i_x in range(x.shape[0]): + sw.add_figure( + f"training_input/x_{i_x}", + plot_spectrogram(x[i_x].cpu()), + steps, + ) + sw.add_audio( + f"training_input/y_{i_x}", + y[i_x][0], + steps, + h.sampling_rate, + ) + + # Seen and unseen speakers validation loops + if not a.debug and steps != 0: + validate( + rank, + a, + h, + validation_loader, + mode=f"seen_{train_loader.dataset.name}", + ) + for i in range(len(list_unseen_validation_loader)): + validate( + rank, + a, + h, + list_unseen_validation_loader[i], + mode=f"unseen_{list_unseen_validation_loader[i].dataset.name}", + ) + steps += 1 + + # BigVGAN-v2 learning rate scheduler is changed from epoch-level to step-level + scheduler_g.step() + scheduler_d.step() + + if rank == 0: + print( + f"Time taken for epoch {epoch + 1} is {int(time.time() - start)} sec\n" + ) + + +def main(): + print("Initializing Training Process..") + + parser = argparse.ArgumentParser() + + parser.add_argument("--group_name", default=None) + + parser.add_argument("--input_wavs_dir", default="LibriTTS") + parser.add_argument("--input_mels_dir", default="ft_dataset") + parser.add_argument( + "--input_training_file", default="tests/LibriTTS/train-full.txt" + ) + parser.add_argument( + "--input_validation_file", default="tests/LibriTTS/val-full.txt" + ) + + parser.add_argument( + "--list_input_unseen_wavs_dir", + nargs="+", + default=["tests/LibriTTS", "tests/LibriTTS"], + ) + parser.add_argument( + "--list_input_unseen_validation_file", + nargs="+", + default=["tests/LibriTTS/dev-clean.txt", "tests/LibriTTS/dev-other.txt"], + ) + + parser.add_argument("--checkpoint_path", default="exp/bigvgan") + parser.add_argument("--config", default="") + + parser.add_argument("--training_epochs", default=100000, type=int) + parser.add_argument("--stdout_interval", default=5, type=int) + parser.add_argument("--checkpoint_interval", default=50000, type=int) + parser.add_argument("--summary_interval", default=100, type=int) + parser.add_argument("--validation_interval", default=50000, type=int) + + parser.add_argument( + "--freeze_step", + default=0, + type=int, + help="freeze D for the first specified steps. G only uses regression loss for these steps.", + ) + + parser.add_argument("--fine_tuning", default=False, type=bool) + + parser.add_argument( + "--debug", + default=False, + type=bool, + help="debug mode. skips validation loop throughout training", + ) + parser.add_argument( + "--evaluate", + default=False, + type=bool, + help="only run evaluation from checkpoint and exit", + ) + parser.add_argument( + "--eval_subsample", + default=5, + type=int, + help="subsampling during evaluation loop", + ) + parser.add_argument( + "--skip_seen", + default=False, + type=bool, + help="skip seen dataset. useful for test set inference", + ) + parser.add_argument( + "--save_audio", + default=False, + type=bool, + help="save audio of test set inference to disk", + ) + + a = parser.parse_args() + + with open(a.config) as f: + data = f.read() + + json_config = json.loads(data) + h = AttrDict(json_config) + + build_env(a.config, "config.json", a.checkpoint_path) + + torch.manual_seed(h.seed) + if torch.cuda.is_available(): + torch.cuda.manual_seed(h.seed) + h.num_gpus = torch.cuda.device_count() + h.batch_size = int(h.batch_size / h.num_gpus) + print(f"Batch size per GPU: {h.batch_size}") + else: + pass + + if h.num_gpus > 1: + mp.spawn( + train, + nprocs=h.num_gpus, + args=( + a, + h, + ), + ) + else: + train(0, a, h) + + +if __name__ == "__main__": + main() diff --git a/src/third_party/BigVGAN/utils.py b/src/third_party/BigVGAN/utils.py new file mode 100644 index 0000000000000000000000000000000000000000..fc64ae591d7ec40e69146e626a7570414495d243 --- /dev/null +++ b/src/third_party/BigVGAN/utils.py @@ -0,0 +1,99 @@ +# Adapted from https://github.com/jik876/hifi-gan under the MIT license. +# LICENSE is in incl_licenses directory. + +import glob +import os +import matplotlib +import torch +from torch.nn.utils import weight_norm + +matplotlib.use("Agg") +import matplotlib.pylab as plt +from meldataset import MAX_WAV_VALUE +from scipy.io.wavfile import write + + +def plot_spectrogram(spectrogram): + fig, ax = plt.subplots(figsize=(10, 2)) + im = ax.imshow(spectrogram, aspect="auto", origin="lower", interpolation="none") + plt.colorbar(im, ax=ax) + + fig.canvas.draw() + plt.close() + + return fig + + +def plot_spectrogram_clipped(spectrogram, clip_max=2.0): + fig, ax = plt.subplots(figsize=(10, 2)) + im = ax.imshow( + spectrogram, + aspect="auto", + origin="lower", + interpolation="none", + vmin=1e-6, + vmax=clip_max, + ) + plt.colorbar(im, ax=ax) + + fig.canvas.draw() + plt.close() + + return fig + + +def init_weights(m, mean=0.0, std=0.01): + classname = m.__class__.__name__ + if classname.find("Conv") != -1: + m.weight.data.normal_(mean, std) + + +def apply_weight_norm(m): + classname = m.__class__.__name__ + if classname.find("Conv") != -1: + weight_norm(m) + + +def get_padding(kernel_size, dilation=1): + return int((kernel_size * dilation - dilation) / 2) + + +def load_checkpoint(filepath, device): + assert os.path.isfile(filepath) + print(f"Loading '{filepath}'") + checkpoint_dict = torch.load(filepath, map_location=device) + print("Complete.") + return checkpoint_dict + + +def save_checkpoint(filepath, obj): + print(f"Saving checkpoint to {filepath}") + torch.save(obj, filepath) + print("Complete.") + + +def scan_checkpoint(cp_dir, prefix, renamed_file=None): + # Fallback to original scanning logic first + pattern = os.path.join(cp_dir, prefix + "????????") + cp_list = glob.glob(pattern) + + if len(cp_list) > 0: + last_checkpoint_path = sorted(cp_list)[-1] + print(f"[INFO] Resuming from checkpoint: '{last_checkpoint_path}'") + return last_checkpoint_path + + # If no pattern-based checkpoints are found, check for renamed file + if renamed_file: + renamed_path = os.path.join(cp_dir, renamed_file) + if os.path.isfile(renamed_path): + print(f"[INFO] Resuming from renamed checkpoint: '{renamed_file}'") + return renamed_path + + return None + + +def save_audio(audio, path, sr): + # wav: torch with 1d shape + audio = audio * MAX_WAV_VALUE + audio = audio.cpu().numpy().astype("int16") + write(path, sr, audio) diff --git a/src/utils/data_processing.py b/src/utils/data_processing.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/tests/.gitkeep b/tests/.gitkeep new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/tests/test_data/1/infer_audio.wav b/tests/test_data/1/infer_audio.wav new file mode 100644 index 0000000000000000000000000000000000000000..e6874d9400dbdb42744c0a53a1337c51ec9b0f42 --- /dev/null +++ b/tests/test_data/1/infer_audio.wav @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2924700ad369afabb4489eceec9c5e1e9c0fae90a3409f480678aba7a79a7378 +size 127020 diff --git a/tests/test_data/1/infer_text.txt b/tests/test_data/1/infer_text.txt new file mode 100644 index 0000000000000000000000000000000000000000..6d98f32bd7b9cf7a74ad956ac291e24bb596f57e --- /dev/null +++ b/tests/test_data/1/infer_text.txt @@ -0,0 +1 @@ +chào mọi người, mọi người khỏe không? \ No newline at end of file diff --git a/tests/test_data/1/refer_audio.mp3 b/tests/test_data/1/refer_audio.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..d2829af3bb370a21dc4051aefa9a82a43cd807a1 Binary files /dev/null and b/tests/test_data/1/refer_audio.mp3 differ diff --git a/tests/test_data/1/refer_text.txt b/tests/test_data/1/refer_text.txt new file mode 100644 index 0000000000000000000000000000000000000000..222bd929455a465b091a9be95d1667d5f8c61b06 --- /dev/null +++ b/tests/test_data/1/refer_text.txt @@ -0,0 +1 @@ +bạn và tôi đều như nhau nhé, rồi chúng ta đi đâu nè \ No newline at end of file diff --git a/tests/test_data/2/infer_audio.mp3 b/tests/test_data/2/infer_audio.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..a11fa1a8f650d704a420cfa47bf54d8fb48409d2 Binary files /dev/null and b/tests/test_data/2/infer_audio.mp3 differ diff --git a/tests/test_data/2/infer_text.txt b/tests/test_data/2/infer_text.txt new file mode 100644 index 0000000000000000000000000000000000000000..6c1829941f691439d5f665e6da59260c4f380a37 --- /dev/null +++ b/tests/test_data/2/infer_text.txt @@ -0,0 +1 @@ +Tôi rất khỏe,cảm ơn mọi người đã quan tâm. \ No newline at end of file diff --git a/tests/test_data/2/refer_audio.mp3 b/tests/test_data/2/refer_audio.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..5a0ae710f3bc9950cc1b480deafd630f87ec2569 Binary files /dev/null and b/tests/test_data/2/refer_audio.mp3 differ diff --git a/tests/test_data/2/refer_text.txt b/tests/test_data/2/refer_text.txt new file mode 100644 index 0000000000000000000000000000000000000000..f8f69be00b3ee5fdd61b4bc1532221172f451d1e --- /dev/null +++ b/tests/test_data/2/refer_text.txt @@ -0,0 +1 @@ +Chúng thường sống hòa bình với các loài động vật khác, kể cả những loài săn mồi. \ No newline at end of file diff --git a/tests/test_data/3/infer_audio.mp3 b/tests/test_data/3/infer_audio.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..ec075f57a4de3d70a14a6fc2c02ac39cf3f1464b Binary files /dev/null and b/tests/test_data/3/infer_audio.mp3 differ diff --git a/tests/test_data/3/infer_text.txt b/tests/test_data/3/infer_text.txt new file mode 100644 index 0000000000000000000000000000000000000000..f66539d36d5a961e75ae51283227824eda32888d --- /dev/null +++ b/tests/test_data/3/infer_text.txt @@ -0,0 +1 @@ +Nhà Tiền Lê, Lý và Trần đã chống trả các cuộc tấn công của nhà Tống và nhà Mông – Nguyên, đều thắng lợi và bảo vệ được Đại Việt. \ No newline at end of file diff --git a/tests/test_data/3/refer_audio.mp3 b/tests/test_data/3/refer_audio.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..02f6db866f8a12752a940df08eb2bae510252a9f --- /dev/null +++ b/tests/test_data/3/refer_audio.mp3 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cd15755a7704fd99247dfae618a4f8e9d9655af735def78e6fdec5467faca641 +size 183110 diff --git a/tests/test_data/3/refer_text.txt b/tests/test_data/3/refer_text.txt new file mode 100644 index 0000000000000000000000000000000000000000..c20226f73f3fe4ae3898079ea752cac5713e2531 --- /dev/null +++ b/tests/test_data/3/refer_text.txt @@ -0,0 +1 @@ +Sau nhà Ngô, lần lượt các triều Đinh, Tiền Lê, Lý và Trần tổ chức chính quyền tương tự các triều đại Trung Hoa, lấy Phật giáo làm tôn giáo chính của quốc gia và cho truyền bá cả Nho giáo và Đạo giáo. \ No newline at end of file diff --git a/tests/test_data/4/infer_audio.mp3 b/tests/test_data/4/infer_audio.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..df9455660b7ec0f0a430bab0e0ddcb2183e68513 Binary files /dev/null and b/tests/test_data/4/infer_audio.mp3 differ diff --git a/tests/test_data/4/infer_text.txt b/tests/test_data/4/infer_text.txt new file mode 100644 index 0000000000000000000000000000000000000000..1b420fadc9cffb4a88a1ee3d10e19fe8b8b34e5e --- /dev/null +++ b/tests/test_data/4/infer_text.txt @@ -0,0 +1 @@ +Người dân Đông Á cổ đại đã uống trà trong nhiều thế kỷ, thậm chí có thể là hàng thiên niên kỷ , trước khi sử dụng nó như một thức uống. \ No newline at end of file diff --git a/tests/test_data/4/refer_audio.mp3 b/tests/test_data/4/refer_audio.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..f7610d4bb88eda8ffa7be4f3350f70b5d32514b9 --- /dev/null +++ b/tests/test_data/4/refer_audio.mp3 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9ea81c8700f5ff2e6497c9beaa942b5ed107e03ae468472d78a4c8c80e3b63af +size 138388 diff --git a/tests/test_data/4/refer_text.txt b/tests/test_data/4/refer_text.txt new file mode 100644 index 0000000000000000000000000000000000000000..ed02670351ce15acdca6a759bb20ef2a9e24a1d5 --- /dev/null +++ b/tests/test_data/4/refer_text.txt @@ -0,0 +1 @@ +Cấu trúc sừng và mào là phổ biến ở tất cả các nhóm khủng long, và vài nhóm thậm chí còn phát triển các biến đổi bộ xương như giáp mô hoặc gai. \ No newline at end of file