Spaces:
Configuration error
Configuration error
Upload 7 files
Browse files- README.md +282 -11
- config.yaml +81 -0
- gradio_app.py +965 -0
- main.py +80 -0
- pyproject.toml +70 -0
- requirements.txt +162 -0
- uv.lock +0 -0
README.md
CHANGED
|
@@ -1,14 +1,285 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
---
|
| 2 |
-
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 12 |
---
|
| 13 |
|
| 14 |
-
|
|
|
|
| 1 |
+
# VieNeu-TTS
|
| 2 |
+
|
| 3 |
+
[](https://github.com/pnnbao97/VieNeu-TTS)
|
| 4 |
+
[](https://huggingface.co/pnnbao-ump/VieNeu-TTS)
|
| 5 |
+
[](https://huggingface.co/pnnbao-ump/VieNeu-TTS-0.3B)
|
| 6 |
+
[](https://huggingface.co/pnnbao-ump/VieNeu-TTS-0.3B-q8-gguf)
|
| 7 |
+
[](https://discord.gg/mQWr4cp3)
|
| 8 |
+
[](https://colab.research.google.com/drive/1V1DjG-KdmurCAhvXrxxTLsa9tteDxSVO?usp=sharing)
|
| 9 |
+
|
| 10 |
+
<img width="899" height="615" alt="Untitled" src="https://github.com/user-attachments/assets/7eb9b816-6ab7-4049-866f-f85e36cb9c6f" />
|
| 11 |
+
|
| 12 |
+
**VieNeu-TTS** is an advanced on-device Vietnamese Text-to-Speech (TTS) model with **instant voice cloning**.
|
| 13 |
+
|
| 14 |
+
> [!TIP]
|
| 15 |
+
> **Voice Cloning:** All model variants (including GGUF) support instant voice cloning with just **3-5 seconds** of reference audio.
|
| 16 |
+
|
| 17 |
+
This project features two core architectures trained on the [VieNeu-TTS-1000h](https://huggingface.co/datasets/pnnbao-ump/VieNeu-TTS-1000h) dataset:
|
| 18 |
+
- **VieNeu-TTS (0.5B):** An enhanced model fine-tuned from the NeuTTS Air architecture for maximum stability.
|
| 19 |
+
- **VieNeu-TTS-0.3B:** A specialized model **trained from scratch**, delivering 2x faster inference and ultra-low latency.
|
| 20 |
+
|
| 21 |
+
These represent a significant upgrade from the previous VieNeu-TTS-140h with the following improvements:
|
| 22 |
+
|
| 23 |
+
- **Enhanced pronunciation**: More accurate and stable Vietnamese pronunciation
|
| 24 |
+
- **Code-switching support**: Seamless transitions between Vietnamese and English
|
| 25 |
+
- **Better voice cloning**: Higher fidelity and speaker consistency
|
| 26 |
+
- **Real-time synthesis**: 24 kHz waveform generation on CPU or GPU
|
| 27 |
+
- **Multiple model formats**: Support for PyTorch, GGUF Q4/Q8 (CPU optimized), and ONNX codec
|
| 28 |
+
|
| 29 |
+
VieNeu-TTS delivers production-ready speech synthesis fully offline.
|
| 30 |
+
|
| 31 |
+
**Author:** Phạm Nguyễn Ngọc Bảo
|
| 32 |
+
|
| 33 |
+
---
|
| 34 |
+
|
| 35 |
+
[<img width="600" height="595" alt="VieNeu-TTS" src="https://github.com/user-attachments/assets/6b32df9d-7e2e-474f-94c8-43d6fa586d15" />](https://github.com/user-attachments/assets/6b32df9d-7e2e-474f-94c8-43d6fa586d15)
|
| 36 |
+
|
| 37 |
+
---
|
| 38 |
+
|
| 39 |
+
## 🔬 Model Overview
|
| 40 |
+
|
| 41 |
+
- **Backbone:**
|
| 42 |
+
- **VieNeu-TTS (0.5B):** Qwen-0.5B fine-tuned from [NeuTTS Air](https://huggingface.co/neuphonic/neutts-air).
|
| 43 |
+
- **VieNeu-TTS-0.3B:** Custom 0.3B model **trained from scratch**, optimized for extreme speed (2x faster).
|
| 44 |
+
- **Audio codec:** NeuCodec (torch implementation; ONNX & quantized variants supported)
|
| 45 |
+
- **Context window:** 2,048 tokens shared by prompt text and speech tokens
|
| 46 |
+
- **Output watermark:** Enabled by default
|
| 47 |
+
- **Training data:** [VieNeu-TTS-1000h](https://huggingface.co/datasets/pnnbao-ump/VieNeu-TTS-1000h) — 443,641 curated Vietnamese samples (Used for both versions).
|
| 48 |
+
|
| 49 |
+
### Model Variants
|
| 50 |
+
|
| 51 |
+
| Model | Format | Device | Quality | Speed |
|
| 52 |
+
| ----------------------- | ------- | ------- | ---------- | ----------------------- |
|
| 53 |
+
| VieNeu-TTS | PyTorch | GPU/CPU | ⭐⭐⭐⭐⭐ | Very Fast with lmdeploy |
|
| 54 |
+
| VieNeu-TTS-0.3B | PyTorch | GPU/CPU | ⭐⭐⭐⭐ | **Ultra Fast (2x)** |
|
| 55 |
+
| VieNeu-TTS-q8-gguf | GGUF Q8 | CPU/GPU | ⭐⭐⭐⭐ | Fast |
|
| 56 |
+
| VieNeu-TTS-q4-gguf | GGUF Q4 | CPU/GPU | ⭐⭐⭐ | Very Fast |
|
| 57 |
+
| VieNeu-TTS-0.3B-q8-gguf | GGUF Q8 | CPU/GPU | ⭐⭐⭐⭐ | **Ultra Fast (1.5x)** |
|
| 58 |
+
| VieNeu-TTS-0.3B-q4-gguf | GGUF Q4 | CPU/GPU | ⭐⭐⭐ | **Extreme Speed (2x)** |
|
| 59 |
+
|
| 60 |
+
**Recommendations:**
|
| 61 |
+
|
| 62 |
+
- **GPU users**: Use `VieNeu-TTS` (PyTorch) for best quality
|
| 63 |
+
- **CPU users**: Use `VieNeu-TTS-0.3B-q4-gguf` for fastest inference or `VieNeu-TTS-0.3B-q8-gguf` for best CPU quality.
|
| 64 |
+
- **Streaming**: Only GGUF models support streaming inference (Requires `llama-cpp-python >= 0.3.16`)
|
| 65 |
+
|
| 66 |
+
---
|
| 67 |
+
|
| 68 |
+
## ✅ Todo & Status
|
| 69 |
+
|
| 70 |
+
- [x] Publish safetensor artifacts
|
| 71 |
+
- [x] Release GGUF Q4 / Q8 models
|
| 72 |
+
- [x] Release datasets (1000h and 140h)
|
| 73 |
+
- [x] Enable streaming on GPU
|
| 74 |
+
- [x] Provide Dockerized setup
|
| 75 |
+
- [ ] Release fine-tuning code
|
| 76 |
+
|
| 77 |
+
---
|
| 78 |
+
|
| 79 |
+
## 🏁 Getting Started
|
| 80 |
+
|
| 81 |
+
### 1. Clone the repository
|
| 82 |
+
```bash
|
| 83 |
+
git clone https://github.com/pnnbao97/VieNeu-TTS.git
|
| 84 |
+
cd VieNeu-TTS
|
| 85 |
+
```
|
| 86 |
+
|
| 87 |
+
### 2. Install eSpeak NG (Required)
|
| 88 |
+
Phonemizer requires eSpeak NG to function.
|
| 89 |
+
|
| 90 |
+
- **Windows:** Download installer from [eSpeak NG Releases](https://github.com/espeak-ng/espeak-ng/releases) (Recommended: `.msi`).
|
| 91 |
+
- **macOS:** `brew install espeak`
|
| 92 |
+
- **Ubuntu/Debian:** `sudo apt install espeak-ng`
|
| 93 |
+
- **Arch Linux:** `paru -S aur/espeak-ng`
|
| 94 |
+
|
| 95 |
+
---
|
| 96 |
+
|
| 97 |
+
### 3. Environment Setup (Choose ONE method)
|
| 98 |
+
|
| 99 |
+
#### Method 1: Standard with `uv` (Recommended)
|
| 100 |
+
This is the fastest and most reliable way to manage dependencies.
|
| 101 |
+
|
| 102 |
+
**A. Install `uv`** (If you haven't already):
|
| 103 |
+
- **Windows:** `powershell -c "irm https://astral.sh/uv/install.ps1 | iex"`
|
| 104 |
+
- **Linux/macOS:** `curl -LsSf https://astral.sh/uv/install.sh | sh`
|
| 105 |
+
|
| 106 |
+
**B. Choose your hardware:**
|
| 107 |
+
|
| 108 |
+
**Option A: For GPU Users (NVIDIA 30xx/40xx/50xx)**
|
| 109 |
+
|
| 110 |
+
> [!IMPORTANT]
|
| 111 |
+
> **Update your NVIDIA Drivers & Install CUDA Toolkit!**
|
| 112 |
+
> This project uses **CUDA 12.8**. Please ensure your NVIDIA driver is up-to-date (support CUDA 12.8 or newer) to avoid compatibility issues, especially on RTX 30 series.
|
| 113 |
+
>
|
| 114 |
+
> To use `lmdeploy`, you **MUST** install the **NVIDIA GPU Computing Toolkit**: [https://developer.nvidia.com/cuda-downloads](https://developer.nvidia.com/cuda-downloads).
|
| 115 |
+
|
| 116 |
+
```bash
|
| 117 |
+
uv sync
|
| 118 |
+
```
|
| 119 |
+
|
| 120 |
+
**Option B: For CPU-only Users**
|
| 121 |
+
|
| 122 |
+
1. Switch to CPU configuration:
|
| 123 |
+
```bash
|
| 124 |
+
# Windows:
|
| 125 |
+
ren pyproject.toml pyproject.toml.bak
|
| 126 |
+
copy pyproject.toml.cpu pyproject.toml
|
| 127 |
+
|
| 128 |
+
# Linux/macOS:
|
| 129 |
+
mv pyproject.toml pyproject.toml.bak
|
| 130 |
+
cp pyproject.toml.cpu pyproject.toml
|
| 131 |
+
```
|
| 132 |
+
2. Install dependencies:
|
| 133 |
+
```bash
|
| 134 |
+
uv sync
|
| 135 |
+
```
|
| 136 |
+
|
| 137 |
+
**C. Run the Application:**
|
| 138 |
+
```bash
|
| 139 |
+
uv run gradio_app.py
|
| 140 |
+
```
|
| 141 |
+
|
| 142 |
+
Then access the Web UI at `http://127.0.0.1:7860`.
|
| 143 |
+
|
| 144 |
+
---
|
| 145 |
+
|
| 146 |
+
#### Method 2: Automatic with Makefile (Alternative)
|
| 147 |
+
Best if you have `make` installed (standard on Linux/macOS, or via Git Bash on Windows). It handles configuration swaps automatically.
|
| 148 |
+
|
| 149 |
+
- **Setup GPU:** `make setup-gpu`
|
| 150 |
+
- **Setup CPU:** `make setup-cpu`
|
| 151 |
+
- **Run Demo:** `make demo`
|
| 152 |
+
|
| 153 |
+
|
| 154 |
+
Then access the Web UI at `http://127.0.0.1:7860`.
|
| 155 |
+
|
| 156 |
+
---
|
| 157 |
+
|
| 158 |
+
---
|
| 159 |
+
|
| 160 |
+
## 🐋 Docker Deployment
|
| 161 |
+
|
| 162 |
+
For a quick start or production deployment without manually installing dependencies, use Docker.
|
| 163 |
+
|
| 164 |
+
### Quick Start
|
| 165 |
+
|
| 166 |
+
Copy .env.example to .env
|
| 167 |
+
|
| 168 |
+
```
|
| 169 |
+
cp .env.example .env
|
| 170 |
+
```
|
| 171 |
+
|
| 172 |
+
Build and start container
|
| 173 |
+
|
| 174 |
+
```bash
|
| 175 |
+
# Run with CPU
|
| 176 |
+
docker compose --profile cpu up
|
| 177 |
+
|
| 178 |
+
# Run with GPU (requires NVIDIA Container Toolkit)
|
| 179 |
+
docker compose --profile gpu up
|
| 180 |
+
```
|
| 181 |
+
|
| 182 |
+
Access the Web UI at `http://localhost:7860`.
|
| 183 |
+
|
| 184 |
+
For detailed deployment instructions, including production setup, see [docs/Deploy.md](docs/Deploy.md).
|
| 185 |
+
|
| 186 |
+
---
|
| 187 |
+
|
| 188 |
+
## 📦 Project Structure
|
| 189 |
+
|
| 190 |
+
```
|
| 191 |
+
VieNeu-TTS/
|
| 192 |
+
├── examples/
|
| 193 |
+
│ ├── infer_long_text.py # CLI for long-form synthesis (chunked)
|
| 194 |
+
│ └── sample_long_text.txt # Example paragraph for testing
|
| 195 |
+
├── gradio_app.py # Local Gradio web demo with LMDeploy support
|
| 196 |
+
├── main.py # Basic batch inference script
|
| 197 |
+
├── config.yaml # Configuration for models, codecs, and voices
|
| 198 |
+
├── output_audio/ # Generated audio (created when running scripts)
|
| 199 |
+
├── sample/ # Reference voices (audio + transcript + codes)
|
| 200 |
+
│ ├── Bình (nam miền Bắc).wav/txt/pt
|
| 201 |
+
│ ├── Đoan (nữ miền Nam).wav/txt/pt
|
| 202 |
+
│ ├── Dung (nữ miền Nam).wav/txt/pt
|
| 203 |
+
│ ├── Hương (nữ miền Bắc).wav/txt/pt
|
| 204 |
+
│ ├── Ly (nữ miền Bắc).wav/txt/pt
|
| 205 |
+
│ ├── Ngọc (nữ miền Bắc).wav/txt/pt
|
| 206 |
+
│ ├── Nguyên (nam miền Nam).wav/txt/pt
|
| 207 |
+
│ ├── Sơn (nam miền Nam).wav/txt/pt
|
| 208 |
+
│ ├── Tuyên (nam miền Bắc).wav/txt/pt
|
| 209 |
+
│ └── Vĩnh (nam miền Nam).wav/txt/pt
|
| 210 |
+
├── utils/
|
| 211 |
+
│ ├── __init__.py
|
| 212 |
+
│ ├── core_utils.py # Text chunking utilities
|
| 213 |
+
│ ├── normalize_text.py # Vietnamese text normalization pipeline
|
| 214 |
+
│ ├── phonemize_text.py # Text to phoneme conversion
|
| 215 |
+
│ └── phoneme_dict.json # Phoneme dictionary
|
| 216 |
+
├── vieneu_tts/
|
| 217 |
+
│ ├── __init__.py # Exports VieNeuTTS and FastVieNeuTTS
|
| 218 |
+
│ └── vieneu_tts.py # Core VieNeuTTS implementation (VieNeuTTS & FastVieNeuTTS)
|
| 219 |
+
├── README.md
|
| 220 |
+
├── requirements.txt # Basic dependencies (legacy)
|
| 221 |
+
├── pyproject.toml # Project configuration with full dependencies (UV)
|
| 222 |
+
└── uv.lock # UV lock file for dependency management
|
| 223 |
+
```
|
| 224 |
+
|
| 225 |
---
|
| 226 |
+
|
| 227 |
+
## 📚 References
|
| 228 |
+
|
| 229 |
+
- [GitHub Repository](https://github.com/pnnbao97/VieNeu-TTS)
|
| 230 |
+
- [Hugging Face Model (0.5B)](https://huggingface.co/pnnbao-ump/VieNeu-TTS)
|
| 231 |
+
- [Hugging Face Model (0.3B)](https://huggingface.co/pnnbao-ump/VieNeu-TTS-0.3B)
|
| 232 |
+
- [VieNeuTTS Fine-tuning Guide](https://github.com/pnnbao-ump/VieNeuTTS/blob/main/finetune.ipynb)
|
| 233 |
+
- [VieNeuCodec dataset](https://huggingface.co/datasets/pnnbao-ump/VieNeuCodec-dataset)
|
| 234 |
+
|
| 235 |
+
---
|
| 236 |
+
|
| 237 |
+
## 📄 License
|
| 238 |
+
|
| 239 |
+
- **VieNeu-TTS (0.5B):** Original terms (Apache 2.0).
|
| 240 |
+
- **VieNeu-TTS-0.3B:** Released under **CC BY-NC 4.0** (Non-Commercial).
|
| 241 |
+
- This version is currently **experimental**.
|
| 242 |
+
- **Commercial use is prohibited** without authorization. Please contact the author for commercial licensing.
|
| 243 |
+
|
| 244 |
+
---
|
| 245 |
+
|
| 246 |
+
## 📑 Citation
|
| 247 |
+
|
| 248 |
+
```bibtex
|
| 249 |
+
@misc{vieneutts2026,
|
| 250 |
+
title = {VieNeu-TTS: Vietnamese Text-to-Speech with Instant Voice Cloning},
|
| 251 |
+
author = {Pham Nguyen Ngoc Bao},
|
| 252 |
+
year = {2026},
|
| 253 |
+
publisher = {Hugging Face},
|
| 254 |
+
howpublished = {\url{https://huggingface.co/pnnbao-ump/VieNeu-TTS}}
|
| 255 |
+
}
|
| 256 |
+
```
|
| 257 |
+
|
| 258 |
+
## 🤝 Contributing
|
| 259 |
+
|
| 260 |
+
Contributions are welcome!
|
| 261 |
+
|
| 262 |
+
1. Fork the repository
|
| 263 |
+
2. Create a feature branch: `git checkout -b feature/amazing-feature`
|
| 264 |
+
3. Commit your changes: `git commit -m "Add amazing feature"`
|
| 265 |
+
4. Push the branch: `git push origin feature/amazing-feature`
|
| 266 |
+
5. Open a pull request
|
| 267 |
+
|
| 268 |
+
---
|
| 269 |
+
|
| 270 |
+
## 📞 Support
|
| 271 |
+
|
| 272 |
+
- GitHub Issues: [github.com/pnnbao97/VieNeu-TTS/issues](https://github.com/pnnbao97/VieNeu-TTS/issues)
|
| 273 |
+
- Hugging Face: [huggingface.co/pnnbao-ump](https://huggingface.co/pnnbao-ump)
|
| 274 |
+
- Discord: [Join with us](https://discord.gg/mQWr4cp3)
|
| 275 |
+
- Facebook: [Phạm Nguyễn Ngọc Bảo](https://www.facebook.com/bao.phamnguyenngoc.5)
|
| 276 |
+
|
| 277 |
+
---
|
| 278 |
+
|
| 279 |
+
## 🙏 Acknowledgements
|
| 280 |
+
|
| 281 |
+
This project builds upon [NeuTTS Air](https://huggingface.co/neuphonic/neutts-air) for the original 0.5B model. The 0.3B version is a custom architecture trained from scratch using the [VieNeu-TTS-1000h](https://huggingface.co/datasets/pnnbao-ump/VieNeu-TTS-1000h) dataset.
|
| 282 |
+
|
| 283 |
---
|
| 284 |
|
| 285 |
+
**Made with ❤️ for the Vietnamese TTS community**
|
config.yaml
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
text_settings:
|
| 2 |
+
max_chars_per_chunk: 256
|
| 3 |
+
max_total_chars_streaming: 3000
|
| 4 |
+
|
| 5 |
+
backbone_configs:
|
| 6 |
+
"VieNeu-TTS (GPU)":
|
| 7 |
+
repo: pnnbao-ump/VieNeu-TTS
|
| 8 |
+
supports_streaming: false
|
| 9 |
+
description: Chất lượng cao nhất, yêu cầu GPU
|
| 10 |
+
"VieNeu-TTS-0.3B (GPU)":
|
| 11 |
+
repo: pnnbao-ump/VieNeu-TTS-0.3B
|
| 12 |
+
supports_streaming: false
|
| 13 |
+
description: Phiên bản nhẹ cho GPU, tốc độ nhanh x2 so với phiên bản gốc
|
| 14 |
+
"VieNeu-TTS-q8-gguf":
|
| 15 |
+
repo: pnnbao-ump/VieNeu-TTS-q8-gguf
|
| 16 |
+
supports_streaming: true
|
| 17 |
+
description: Phiên bản GGUF có chất lượng cao nhất
|
| 18 |
+
"VieNeu-TTS-q4-gguf":
|
| 19 |
+
repo: pnnbao-ump/VieNeu-TTS-q4-gguf
|
| 20 |
+
supports_streaming: true
|
| 21 |
+
description: Cân bằng giữa chất lượng và tốc độ
|
| 22 |
+
"VieNeu-TTS-0.3B-q4-gguf":
|
| 23 |
+
repo: pnnbao-ump/VieNeu-TTS-0.3B-q4-gguf
|
| 24 |
+
supports_streaming: true
|
| 25 |
+
description: Phiên bản cực nhẹ, chạy mượt trên CPU
|
| 26 |
+
"VieNeu-TTS-0.3B-q8-gguf":
|
| 27 |
+
repo: pnnbao-ump/VieNeu-TTS-0.3B-q8-gguf
|
| 28 |
+
supports_streaming: true
|
| 29 |
+
description: Phiên bản 0.3B nén Q8, chất lượng cao và tốc độ nhanh trên CPU
|
| 30 |
+
|
| 31 |
+
codec_configs:
|
| 32 |
+
"NeuCodec (Standard)":
|
| 33 |
+
repo: neuphonic/neucodec
|
| 34 |
+
description: Codec chuẩn, tốc độ trung bình
|
| 35 |
+
use_preencoded: false
|
| 36 |
+
"NeuCodec (Distill)":
|
| 37 |
+
repo: neuphonic/distill-neucodec
|
| 38 |
+
description: Codec tối ưu, tốc độ cao
|
| 39 |
+
use_preencoded: false
|
| 40 |
+
"NeuCodec ONNX (Fast CPU)":
|
| 41 |
+
repo: neuphonic/neucodec-onnx-decoder-int8
|
| 42 |
+
description: Tối ưu cho CPU, cần pre-encoded codes
|
| 43 |
+
use_preencoded: true
|
| 44 |
+
|
| 45 |
+
voice_samples:
|
| 46 |
+
"Tuyên (nam miền Bắc)":
|
| 47 |
+
audio: ./sample/Tuyên (nam miền Bắc).wav
|
| 48 |
+
text: ./sample/Tuyên (nam miền Bắc).txt
|
| 49 |
+
codes: ./sample/Tuyên (nam miền Bắc).pt
|
| 50 |
+
"Vĩnh (nam miền Nam)":
|
| 51 |
+
audio: ./sample/Vĩnh (nam miền Nam).wav
|
| 52 |
+
text: ./sample/Vĩnh (nam miền Nam).txt
|
| 53 |
+
codes: ./sample/Vĩnh (nam miền Nam).pt
|
| 54 |
+
"Bình (nam miền Bắc)":
|
| 55 |
+
audio: ./sample/Bình (nam miền Bắc).wav
|
| 56 |
+
text: ./sample/Bình (nam miền Bắc).txt
|
| 57 |
+
codes: ./sample/Bình (nam miền Bắc).pt
|
| 58 |
+
"Nguyên (nam miền Nam)":
|
| 59 |
+
audio: ./sample/Nguyên (nam miền Nam).wav
|
| 60 |
+
text: ./sample/Nguyên (nam miền Nam).txt
|
| 61 |
+
codes: ./sample/Nguyên (nam miền Nam).pt
|
| 62 |
+
"Sơn (nam miền Nam)":
|
| 63 |
+
audio: ./sample/Sơn (nam miền Nam).wav
|
| 64 |
+
text: ./sample/Sơn (nam miền Nam).txt
|
| 65 |
+
codes: ./sample/Sơn (nam miền Nam).pt
|
| 66 |
+
"Đoan (nữ miền Nam)":
|
| 67 |
+
audio: ./sample/Đoan (nữ miền Nam).wav
|
| 68 |
+
text: ./sample/Đoan (nữ miền Nam).txt
|
| 69 |
+
codes: ./sample/Đoan (nữ miền Nam).pt
|
| 70 |
+
"Ngọc (nữ miền Bắc)":
|
| 71 |
+
audio: ./sample/Ngọc (nữ miền Bắc).wav
|
| 72 |
+
text: ./sample/Ngọc (nữ miền Bắc).txt
|
| 73 |
+
codes: ./sample/Ngọc (nữ miền Bắc).pt
|
| 74 |
+
"Ly (nữ miền Bắc)":
|
| 75 |
+
audio: ./sample/Ly (nữ miền Bắc).wav
|
| 76 |
+
text: ./sample/Ly (nữ miền Bắc).txt
|
| 77 |
+
codes: ./sample/Ly (nữ miền Bắc).pt
|
| 78 |
+
"Dung (nữ miền Nam)":
|
| 79 |
+
audio: ./sample/Dung (nữ miền Nam).wav
|
| 80 |
+
text: ./sample/Dung (nữ miền Nam).txt
|
| 81 |
+
codes: ./sample/Dung (nữ miền Nam).pt
|
gradio_app.py
ADDED
|
@@ -0,0 +1,965 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import gradio as gr
|
| 2 |
+
print("⏳ Đang khởi động... Vui lòng chờ...")
|
| 3 |
+
import soundfile as sf
|
| 4 |
+
import tempfile
|
| 5 |
+
import torch
|
| 6 |
+
from vieneu_tts import VieNeuTTS, FastVieNeuTTS
|
| 7 |
+
import os
|
| 8 |
+
import sys
|
| 9 |
+
import time
|
| 10 |
+
import numpy as np
|
| 11 |
+
from typing import Generator, Optional, Tuple
|
| 12 |
+
import queue
|
| 13 |
+
import threading
|
| 14 |
+
import yaml
|
| 15 |
+
from utils.core_utils import split_text_into_chunks, env_bool
|
| 16 |
+
from functools import lru_cache
|
| 17 |
+
import gc
|
| 18 |
+
|
| 19 |
+
print("⏳ Đang khởi động VieNeu-TTS...")
|
| 20 |
+
|
| 21 |
+
# --- CONSTANTS & CONFIG ---
|
| 22 |
+
CONFIG_PATH = os.path.join(os.path.dirname(__file__), "config.yaml")
|
| 23 |
+
try:
|
| 24 |
+
with open(CONFIG_PATH, "r", encoding="utf-8") as f:
|
| 25 |
+
_config = yaml.safe_load(f) or {}
|
| 26 |
+
except Exception as e:
|
| 27 |
+
raise RuntimeError(f"Không thể đọc config.yaml: {e}")
|
| 28 |
+
|
| 29 |
+
BACKBONE_CONFIGS = _config.get("backbone_configs", {})
|
| 30 |
+
CODEC_CONFIGS = _config.get("codec_configs", {})
|
| 31 |
+
VOICE_SAMPLES = _config.get("voice_samples", {})
|
| 32 |
+
|
| 33 |
+
_text_settings = _config.get("text_settings", {})
|
| 34 |
+
MAX_CHARS_PER_CHUNK = _text_settings.get("max_chars_per_chunk", 256)
|
| 35 |
+
MAX_TOTAL_CHARS_STREAMING = _text_settings.get("max_total_chars_streaming", 3000)
|
| 36 |
+
|
| 37 |
+
if not BACKBONE_CONFIGS or not CODEC_CONFIGS:
|
| 38 |
+
raise ValueError("config.yaml thiếu backbone_configs hoặc codec_configs")
|
| 39 |
+
if not VOICE_SAMPLES:
|
| 40 |
+
raise ValueError("config.yaml thiếu voice_samples")
|
| 41 |
+
|
| 42 |
+
# --- 1. MODEL CONFIGURATION ---
|
| 43 |
+
# Global model instance
|
| 44 |
+
tts = None
|
| 45 |
+
current_backbone = None
|
| 46 |
+
current_codec = None
|
| 47 |
+
model_loaded = False
|
| 48 |
+
using_lmdeploy = False
|
| 49 |
+
|
| 50 |
+
# Cache for reference texts
|
| 51 |
+
_ref_text_cache = {}
|
| 52 |
+
|
| 53 |
+
def get_available_devices() -> list[str]:
|
| 54 |
+
"""Get list of available devices for current platform."""
|
| 55 |
+
devices = ["Auto", "CPU"]
|
| 56 |
+
|
| 57 |
+
if sys.platform == "darwin":
|
| 58 |
+
# macOS - check MPS
|
| 59 |
+
if torch.backends.mps.is_available():
|
| 60 |
+
devices.append("MPS")
|
| 61 |
+
else:
|
| 62 |
+
# Windows/Linux - check CUDA
|
| 63 |
+
if torch.cuda.is_available():
|
| 64 |
+
devices.append("CUDA")
|
| 65 |
+
|
| 66 |
+
return devices
|
| 67 |
+
|
| 68 |
+
def get_model_status_message() -> str:
|
| 69 |
+
"""Reconstruct status message from global state"""
|
| 70 |
+
global model_loaded, tts, using_lmdeploy, current_backbone, current_codec
|
| 71 |
+
if not model_loaded or tts is None:
|
| 72 |
+
return "⏳ Chưa tải model."
|
| 73 |
+
|
| 74 |
+
backbone_config = BACKBONE_CONFIGS.get(current_backbone, {})
|
| 75 |
+
codec_config = CODEC_CONFIGS.get(current_codec, {})
|
| 76 |
+
|
| 77 |
+
backend_name = "🚀 LMDeploy (Optimized)" if using_lmdeploy else "📦 Standard"
|
| 78 |
+
|
| 79 |
+
# We don't track the exact device strings perfectly in global state, so we estimate
|
| 80 |
+
device_info = "GPU" if using_lmdeploy else "Auto"
|
| 81 |
+
codec_device = "CPU" if "ONNX" in (current_codec or "") else ("GPU/MPS" if torch.cuda.is_available() or torch.backends.mps.is_available() else "CPU")
|
| 82 |
+
|
| 83 |
+
preencoded_note = "\n⚠️ Codec ONNX không hỗ trợ chức năng clone giọng nói." if codec_config.get('use_preencoded') else ""
|
| 84 |
+
|
| 85 |
+
opt_info = ""
|
| 86 |
+
if using_lmdeploy and hasattr(tts, 'get_optimization_stats'):
|
| 87 |
+
stats = tts.get_optimization_stats()
|
| 88 |
+
opt_info = (
|
| 89 |
+
f"\n\n🔧 Tối ưu hóa:"
|
| 90 |
+
f"\n • Triton: {'✅' if stats['triton_enabled'] else '❌'}"
|
| 91 |
+
f"\n • Max Batch Size (Default): {stats.get('max_batch_size', 'N/A')}"
|
| 92 |
+
f"\n • Reference Cache: {stats['cached_references']} voices"
|
| 93 |
+
f"\n • Prefix Caching: ✅"
|
| 94 |
+
)
|
| 95 |
+
|
| 96 |
+
return (
|
| 97 |
+
f"✅ Model đã tải thành công!\n\n"
|
| 98 |
+
f"🔧 Backend: {backend_name}\n"
|
| 99 |
+
f"🦜 Backbone: {current_backbone}\n"
|
| 100 |
+
f"🎵 Codec: {current_codec}{preencoded_note}{opt_info}"
|
| 101 |
+
)
|
| 102 |
+
|
| 103 |
+
def restore_ui_state():
|
| 104 |
+
"""Update UI components based on persistence"""
|
| 105 |
+
global model_loaded
|
| 106 |
+
msg = get_model_status_message()
|
| 107 |
+
return (
|
| 108 |
+
msg,
|
| 109 |
+
gr.update(interactive=model_loaded), # btn_generate
|
| 110 |
+
gr.update(interactive=False) # btn_stop
|
| 111 |
+
)
|
| 112 |
+
|
| 113 |
+
def should_use_lmdeploy(backbone_choice: str, device_choice: str) -> bool:
|
| 114 |
+
"""Determine if we should use LMDeploy backend."""
|
| 115 |
+
# LMDeploy not supported on macOS
|
| 116 |
+
if sys.platform == "darwin":
|
| 117 |
+
return False
|
| 118 |
+
|
| 119 |
+
if "gguf" in backbone_choice.lower():
|
| 120 |
+
return False
|
| 121 |
+
|
| 122 |
+
if device_choice == "Auto":
|
| 123 |
+
has_gpu = torch.cuda.is_available()
|
| 124 |
+
elif device_choice == "CUDA":
|
| 125 |
+
has_gpu = torch.cuda.is_available()
|
| 126 |
+
else:
|
| 127 |
+
has_gpu = False
|
| 128 |
+
|
| 129 |
+
return has_gpu
|
| 130 |
+
|
| 131 |
+
@lru_cache(maxsize=32)
|
| 132 |
+
def get_ref_text_cached(text_path: str) -> str:
|
| 133 |
+
"""Cache reference text loading"""
|
| 134 |
+
with open(text_path, "r", encoding="utf-8") as f:
|
| 135 |
+
return f.read()
|
| 136 |
+
|
| 137 |
+
def cleanup_gpu_memory():
|
| 138 |
+
"""Aggressively cleanup GPU memory"""
|
| 139 |
+
if torch.cuda.is_available():
|
| 140 |
+
torch.cuda.empty_cache()
|
| 141 |
+
torch.cuda.synchronize()
|
| 142 |
+
elif torch.backends.mps.is_available():
|
| 143 |
+
torch.mps.empty_cache()
|
| 144 |
+
gc.collect()
|
| 145 |
+
|
| 146 |
+
def load_model(backbone_choice: str, codec_choice: str, device_choice: str,
|
| 147 |
+
force_lmdeploy: bool):
|
| 148 |
+
"""Load model with optimizations and max batch size control"""
|
| 149 |
+
global tts, current_backbone, current_codec, model_loaded, using_lmdeploy
|
| 150 |
+
lmdeploy_error_reason = None
|
| 151 |
+
|
| 152 |
+
yield (
|
| 153 |
+
"⏳ Đang tải model với tối ưu hóa... Lưu ý: Quá trình này sẽ tốn thời gian. Vui lòng kiên nhẫn.",
|
| 154 |
+
gr.update(interactive=False),
|
| 155 |
+
gr.update(interactive=False),
|
| 156 |
+
gr.update(interactive=False)
|
| 157 |
+
)
|
| 158 |
+
|
| 159 |
+
try:
|
| 160 |
+
# Cleanup before loading new model
|
| 161 |
+
if model_loaded and tts is not None:
|
| 162 |
+
del tts
|
| 163 |
+
cleanup_gpu_memory()
|
| 164 |
+
|
| 165 |
+
backbone_config = BACKBONE_CONFIGS[backbone_choice]
|
| 166 |
+
codec_config = CODEC_CONFIGS[codec_choice]
|
| 167 |
+
|
| 168 |
+
use_lmdeploy = force_lmdeploy and should_use_lmdeploy(backbone_choice, device_choice)
|
| 169 |
+
|
| 170 |
+
if use_lmdeploy:
|
| 171 |
+
lmdeploy_error_reason = None
|
| 172 |
+
print(f"🚀 Using LMDeploy backend with optimizations")
|
| 173 |
+
|
| 174 |
+
backbone_device = "cuda"
|
| 175 |
+
|
| 176 |
+
if "ONNX" in codec_choice:
|
| 177 |
+
codec_device = "cpu"
|
| 178 |
+
else:
|
| 179 |
+
codec_device = "cuda" if torch.cuda.is_available() else "cpu"
|
| 180 |
+
|
| 181 |
+
print(f"📦 Loading optimized model...")
|
| 182 |
+
print(f" Backbone: {backbone_config['repo']} on {backbone_device}")
|
| 183 |
+
print(f" Codec: {codec_config['repo']} on {codec_device}")
|
| 184 |
+
print(f" Triton: Enabled")
|
| 185 |
+
|
| 186 |
+
try:
|
| 187 |
+
tts = FastVieNeuTTS(
|
| 188 |
+
backbone_repo=backbone_config["repo"],
|
| 189 |
+
backbone_device=backbone_device,
|
| 190 |
+
codec_repo=codec_config["repo"],
|
| 191 |
+
codec_device=codec_device,
|
| 192 |
+
memory_util=0.3,
|
| 193 |
+
tp=1,
|
| 194 |
+
enable_prefix_caching=True,
|
| 195 |
+
enable_triton=True,
|
| 196 |
+
)
|
| 197 |
+
using_lmdeploy = True
|
| 198 |
+
|
| 199 |
+
# Pre-cache voice references
|
| 200 |
+
print("📝 Pre-caching voice references...")
|
| 201 |
+
for voice_name, voice_info in VOICE_SAMPLES.items():
|
| 202 |
+
audio_path = voice_info["audio"]
|
| 203 |
+
text_path = voice_info["text"]
|
| 204 |
+
if os.path.exists(audio_path) and os.path.exists(text_path):
|
| 205 |
+
ref_text = get_ref_text_cached(text_path)
|
| 206 |
+
tts.get_cached_reference(voice_name, audio_path, ref_text)
|
| 207 |
+
print(f" ✅ Cached {len(VOICE_SAMPLES)} voices")
|
| 208 |
+
|
| 209 |
+
except Exception as e:
|
| 210 |
+
import traceback
|
| 211 |
+
traceback.print_exc()
|
| 212 |
+
|
| 213 |
+
error_str = str(e)
|
| 214 |
+
if "$env:CUDA_PATH" in error_str:
|
| 215 |
+
lmdeploy_error_reason = "Không tìm thấy biến môi trường CUDA_PATH. Vui lòng cài đặt NVIDIA GPU Computing Toolkit."
|
| 216 |
+
else:
|
| 217 |
+
lmdeploy_error_reason = f"{error_str}"
|
| 218 |
+
|
| 219 |
+
yield (
|
| 220 |
+
f"⚠️ LMDeploy Init Error: {lmdeploy_error_reason}. Đang loading model với backend mặc định - tốc độ chậm hơn so với lmdeploy...",
|
| 221 |
+
gr.update(interactive=False),
|
| 222 |
+
gr.update(interactive=False)
|
| 223 |
+
)
|
| 224 |
+
time.sleep(1)
|
| 225 |
+
use_lmdeploy = False
|
| 226 |
+
using_lmdeploy = False
|
| 227 |
+
|
| 228 |
+
if not use_lmdeploy:
|
| 229 |
+
print(f"📦 Using original backend")
|
| 230 |
+
|
| 231 |
+
if device_choice == "Auto":
|
| 232 |
+
if "gguf" in backbone_choice.lower():
|
| 233 |
+
# GGUF: uses Metal on Mac, CUDA on Windows/Linux
|
| 234 |
+
if sys.platform == "darwin":
|
| 235 |
+
backbone_device = "gpu" # llama-cpp-python uses Metal
|
| 236 |
+
else:
|
| 237 |
+
backbone_device = "gpu" if torch.cuda.is_available() else "cpu"
|
| 238 |
+
else:
|
| 239 |
+
# PyTorch model
|
| 240 |
+
if sys.platform == "darwin":
|
| 241 |
+
backbone_device = "mps" if torch.backends.mps.is_available() else "cpu"
|
| 242 |
+
else:
|
| 243 |
+
backbone_device = "cuda" if torch.cuda.is_available() else "cpu"
|
| 244 |
+
|
| 245 |
+
# Codec device
|
| 246 |
+
if "ONNX" in codec_choice:
|
| 247 |
+
codec_device = "cpu"
|
| 248 |
+
elif sys.platform == "darwin":
|
| 249 |
+
codec_device = "mps" if torch.backends.mps.is_available() else "cpu"
|
| 250 |
+
else:
|
| 251 |
+
codec_device = "cuda" if torch.cuda.is_available() else "cpu"
|
| 252 |
+
|
| 253 |
+
elif device_choice == "MPS":
|
| 254 |
+
backbone_device = "mps"
|
| 255 |
+
codec_device = "mps" if "ONNX" not in codec_choice else "cpu"
|
| 256 |
+
|
| 257 |
+
else:
|
| 258 |
+
backbone_device = device_choice.lower()
|
| 259 |
+
codec_device = device_choice.lower()
|
| 260 |
+
|
| 261 |
+
if "ONNX" in codec_choice:
|
| 262 |
+
codec_device = "cpu"
|
| 263 |
+
|
| 264 |
+
if "gguf" in backbone_choice.lower() and backbone_device == "cuda":
|
| 265 |
+
backbone_device = "gpu"
|
| 266 |
+
|
| 267 |
+
print(f"📦 Loading model...")
|
| 268 |
+
print(f" Backbone: {backbone_config['repo']} on {backbone_device}")
|
| 269 |
+
print(f" Codec: {codec_config['repo']} on {codec_device}")
|
| 270 |
+
|
| 271 |
+
tts = VieNeuTTS(
|
| 272 |
+
backbone_repo=backbone_config["repo"],
|
| 273 |
+
backbone_device=backbone_device,
|
| 274 |
+
codec_repo=codec_config["repo"],
|
| 275 |
+
codec_device=codec_device
|
| 276 |
+
)
|
| 277 |
+
using_lmdeploy = False
|
| 278 |
+
|
| 279 |
+
current_backbone = backbone_choice
|
| 280 |
+
current_codec = codec_choice
|
| 281 |
+
model_loaded = True
|
| 282 |
+
|
| 283 |
+
# Success message with optimization info
|
| 284 |
+
backend_name = "🚀 LMDeploy (Optimized)" if using_lmdeploy else "📦 Standard"
|
| 285 |
+
device_info = "cuda" if use_lmdeploy else (backbone_device if not use_lmdeploy else "N/A")
|
| 286 |
+
|
| 287 |
+
streaming_support = "✅ Có" if backbone_config['supports_streaming'] else "❌ Không"
|
| 288 |
+
preencoded_note = "\n⚠️ Codec này cần sử dụng pre-encoded codes (.pt files)" if codec_config['use_preencoded'] else ""
|
| 289 |
+
|
| 290 |
+
opt_info = ""
|
| 291 |
+
if using_lmdeploy and hasattr(tts, 'get_optimization_stats'):
|
| 292 |
+
stats = tts.get_optimization_stats()
|
| 293 |
+
opt_info = (
|
| 294 |
+
f"\n\n🔧 Tối ưu hóa:"
|
| 295 |
+
f"\n • Triton: {'✅' if stats['triton_enabled'] else '❌'}"
|
| 296 |
+
f"\n • Max Batch Size (Default): {stats.get('max_batch_size', 'N/A')}"
|
| 297 |
+
f"\n • Reference Cache: {stats['cached_references']} voices"
|
| 298 |
+
f"\n • Prefix Caching: ✅"
|
| 299 |
+
)
|
| 300 |
+
|
| 301 |
+
warning_msg = ""
|
| 302 |
+
if lmdeploy_error_reason:
|
| 303 |
+
warning_msg = (
|
| 304 |
+
f"\n\n⚠️ **Cảnh báo:** Không thể kích hoạt LMDeploy (Optimized Backend) do lỗi sau:\n"
|
| 305 |
+
f"👉 {lmdeploy_error_reason}\n"
|
| 306 |
+
f"💡 Hệ thống đã tự động chuyển về chế độ Standard (chậm hơn)."
|
| 307 |
+
)
|
| 308 |
+
|
| 309 |
+
success_msg = get_model_status_message()
|
| 310 |
+
if warning_msg:
|
| 311 |
+
success_msg += warning_msg
|
| 312 |
+
|
| 313 |
+
yield (
|
| 314 |
+
success_msg,
|
| 315 |
+
gr.update(interactive=True), # btn_generate
|
| 316 |
+
gr.update(interactive=True), # btn_load
|
| 317 |
+
gr.update(interactive=False) # btn_stop
|
| 318 |
+
)
|
| 319 |
+
|
| 320 |
+
except Exception as e:
|
| 321 |
+
import traceback
|
| 322 |
+
traceback.print_exc()
|
| 323 |
+
model_loaded = False
|
| 324 |
+
using_lmdeploy = False
|
| 325 |
+
|
| 326 |
+
if "$env:CUDA_PATH" in str(e):
|
| 327 |
+
yield (
|
| 328 |
+
"❌ Lỗi khi tải model: Không tìm thấy biến môi trường CUDA_PATH. Vui lòng cài đặt NVIDIA GPU Computing Toolkit (https://developer.nvidia.com/cuda/toolkit)",
|
| 329 |
+
gr.update(interactive=False),
|
| 330 |
+
gr.update(interactive=True),
|
| 331 |
+
gr.update(interactive=False)
|
| 332 |
+
)
|
| 333 |
+
else:
|
| 334 |
+
yield (
|
| 335 |
+
f"❌ Lỗi khi tải model: {str(e)}",
|
| 336 |
+
gr.update(interactive=False),
|
| 337 |
+
gr.update(interactive=True),
|
| 338 |
+
gr.update(interactive=False)
|
| 339 |
+
)
|
| 340 |
+
|
| 341 |
+
|
| 342 |
+
# --- 2. DATA & HELPERS ---
|
| 343 |
+
GGUF_ALLOWED_VOICES = [
|
| 344 |
+
"Vĩnh (nam miền Nam)",
|
| 345 |
+
"Bình (nam miền Bắc)",
|
| 346 |
+
"Ngọc (nữ miền Bắc)",
|
| 347 |
+
"Dung (nữ miền Nam)",
|
| 348 |
+
]
|
| 349 |
+
|
| 350 |
+
def get_voice_options(backbone_choice: str):
|
| 351 |
+
"""Filter voice options: GGUF only shows the 4 allowed voices."""
|
| 352 |
+
if "gguf" in backbone_choice.lower():
|
| 353 |
+
return [v for v in GGUF_ALLOWED_VOICES if v in VOICE_SAMPLES]
|
| 354 |
+
return list(VOICE_SAMPLES.keys())
|
| 355 |
+
|
| 356 |
+
def update_voice_dropdown(backbone_choice: str, current_voice: str):
|
| 357 |
+
options = get_voice_options(backbone_choice)
|
| 358 |
+
new_value = current_voice if current_voice in options else (options[0] if options else None)
|
| 359 |
+
return gr.update(choices=options, value=new_value)
|
| 360 |
+
|
| 361 |
+
# --- 3. CORE LOGIC FUNCTIONS ---
|
| 362 |
+
def load_reference_info(voice_choice: str) -> Tuple[Optional[str], str]:
|
| 363 |
+
"""Load reference audio and text with caching"""
|
| 364 |
+
if voice_choice in VOICE_SAMPLES:
|
| 365 |
+
audio_path = VOICE_SAMPLES[voice_choice]["audio"]
|
| 366 |
+
text_path = VOICE_SAMPLES[voice_choice]["text"]
|
| 367 |
+
try:
|
| 368 |
+
if os.path.exists(text_path):
|
| 369 |
+
ref_text = get_ref_text_cached(text_path)
|
| 370 |
+
return audio_path, ref_text
|
| 371 |
+
else:
|
| 372 |
+
return audio_path, "⚠️ Không tìm thấy file text mẫu."
|
| 373 |
+
except Exception as e:
|
| 374 |
+
return None, f"❌ Lỗi: {str(e)}"
|
| 375 |
+
return None, ""
|
| 376 |
+
|
| 377 |
+
def synthesize_speech(text: str, voice_choice: str, custom_audio, custom_text: str,
|
| 378 |
+
mode_tab: str, generation_mode: str, use_batch: bool, max_batch_size_run: int):
|
| 379 |
+
"""Synthesis with optimization support and max batch size control"""
|
| 380 |
+
global tts, current_backbone, current_codec, model_loaded, using_lmdeploy
|
| 381 |
+
|
| 382 |
+
if not model_loaded or tts is None:
|
| 383 |
+
yield None, "⚠️ Vui lòng tải model trước!"
|
| 384 |
+
return
|
| 385 |
+
|
| 386 |
+
if not text or text.strip() == "":
|
| 387 |
+
yield None, "⚠️ Vui lòng nhập văn bản!"
|
| 388 |
+
return
|
| 389 |
+
|
| 390 |
+
raw_text = text.strip()
|
| 391 |
+
|
| 392 |
+
codec_config = CODEC_CONFIGS[current_codec]
|
| 393 |
+
use_preencoded = codec_config['use_preencoded']
|
| 394 |
+
|
| 395 |
+
# Setup Reference
|
| 396 |
+
if mode_tab == "custom_mode":
|
| 397 |
+
if custom_audio is None or not custom_text:
|
| 398 |
+
yield None, "⚠️ Thiếu Audio hoặc Text mẫu custom."
|
| 399 |
+
return
|
| 400 |
+
ref_audio_path = custom_audio
|
| 401 |
+
ref_text_raw = custom_text
|
| 402 |
+
ref_codes_path = None
|
| 403 |
+
else:
|
| 404 |
+
if voice_choice not in VOICE_SAMPLES:
|
| 405 |
+
yield None, "⚠️ Vui lòng chọn giọng mẫu."
|
| 406 |
+
return
|
| 407 |
+
ref_audio_path = VOICE_SAMPLES[voice_choice]["audio"]
|
| 408 |
+
text_path = VOICE_SAMPLES[voice_choice]["text"]
|
| 409 |
+
ref_codes_path = VOICE_SAMPLES[voice_choice]["codes"]
|
| 410 |
+
|
| 411 |
+
if not os.path.exists(ref_audio_path):
|
| 412 |
+
yield None, "❌ Không tìm thấy file audio mẫu."
|
| 413 |
+
return
|
| 414 |
+
|
| 415 |
+
ref_text_raw = get_ref_text_cached(text_path)
|
| 416 |
+
|
| 417 |
+
yield None, "📄 Đang xử lý Reference..."
|
| 418 |
+
|
| 419 |
+
# Encode or get cached reference
|
| 420 |
+
try:
|
| 421 |
+
if use_preencoded and ref_codes_path and os.path.exists(ref_codes_path):
|
| 422 |
+
ref_codes = torch.load(ref_codes_path, map_location="cpu", weights_only=True)
|
| 423 |
+
else:
|
| 424 |
+
# Use cached reference if available (LMDeploy only)
|
| 425 |
+
if using_lmdeploy and hasattr(tts, 'get_cached_reference') and mode_tab == "preset_mode":
|
| 426 |
+
ref_codes = tts.get_cached_reference(voice_choice, ref_audio_path, ref_text_raw)
|
| 427 |
+
else:
|
| 428 |
+
ref_codes = tts.encode_reference(ref_audio_path)
|
| 429 |
+
|
| 430 |
+
if isinstance(ref_codes, torch.Tensor):
|
| 431 |
+
ref_codes = ref_codes.cpu().numpy()
|
| 432 |
+
except Exception as e:
|
| 433 |
+
yield None, f"❌ Lỗi xử lý reference: {e}"
|
| 434 |
+
return
|
| 435 |
+
|
| 436 |
+
text_chunks = split_text_into_chunks(raw_text, max_chars=MAX_CHARS_PER_CHUNK)
|
| 437 |
+
total_chunks = len(text_chunks)
|
| 438 |
+
|
| 439 |
+
# === STANDARD MODE ===
|
| 440 |
+
if generation_mode == "Standard (Một lần)":
|
| 441 |
+
backend_name = "LMDeploy" if using_lmdeploy else "Standard"
|
| 442 |
+
batch_info = " (Batch Mode)" if use_batch and using_lmdeploy and total_chunks > 1 else ""
|
| 443 |
+
|
| 444 |
+
# Show batch size info
|
| 445 |
+
batch_size_info = ""
|
| 446 |
+
if use_batch and using_lmdeploy and hasattr(tts, 'max_batch_size'):
|
| 447 |
+
batch_size_info = f" [Max batch: {tts.max_batch_size}]"
|
| 448 |
+
|
| 449 |
+
yield None, f"🚀 Bắt đầu tổng hợp {backend_name}{batch_info}{batch_size_info} ({total_chunks} đoạn)..."
|
| 450 |
+
|
| 451 |
+
all_audio_segments = []
|
| 452 |
+
sr = 24000
|
| 453 |
+
silence_pad = np.zeros(int(sr * 0.15), dtype=np.float32)
|
| 454 |
+
|
| 455 |
+
start_time = time.time()
|
| 456 |
+
|
| 457 |
+
try:
|
| 458 |
+
# Use batch processing if enabled and using LMDeploy
|
| 459 |
+
if use_batch and using_lmdeploy and hasattr(tts, 'infer_batch') and total_chunks > 1:
|
| 460 |
+
# Show how many mini-batches will be processed
|
| 461 |
+
num_batches = (total_chunks + max_batch_size_run - 1) // max_batch_size_run
|
| 462 |
+
|
| 463 |
+
yield None, f"⚡ Xử lý {num_batches} mini-batch(es) (max {max_batch_size_run} đoạn/batch)..."
|
| 464 |
+
|
| 465 |
+
chunk_wavs = tts.infer_batch(text_chunks, ref_codes, ref_text_raw, max_batch_size=max_batch_size_run)
|
| 466 |
+
|
| 467 |
+
for i, chunk_wav in enumerate(chunk_wavs):
|
| 468 |
+
if chunk_wav is not None and len(chunk_wav) > 0:
|
| 469 |
+
all_audio_segments.append(chunk_wav)
|
| 470 |
+
if i < total_chunks - 1:
|
| 471 |
+
all_audio_segments.append(silence_pad)
|
| 472 |
+
else:
|
| 473 |
+
# Sequential processing
|
| 474 |
+
for i, chunk in enumerate(text_chunks):
|
| 475 |
+
yield None, f"⏳ Đang xử lý đoạn {i+1}/{total_chunks}..."
|
| 476 |
+
|
| 477 |
+
chunk_wav = tts.infer(chunk, ref_codes, ref_text_raw)
|
| 478 |
+
|
| 479 |
+
if chunk_wav is not None and len(chunk_wav) > 0:
|
| 480 |
+
all_audio_segments.append(chunk_wav)
|
| 481 |
+
if i < total_chunks - 1:
|
| 482 |
+
all_audio_segments.append(silence_pad)
|
| 483 |
+
|
| 484 |
+
if not all_audio_segments:
|
| 485 |
+
yield None, "❌ Không sinh được audio nào."
|
| 486 |
+
return
|
| 487 |
+
|
| 488 |
+
yield None, "💾 Đang ghép file và lưu..."
|
| 489 |
+
|
| 490 |
+
final_wav = np.concatenate(all_audio_segments)
|
| 491 |
+
with tempfile.NamedTemporaryFile(delete=False, suffix=".wav") as tmp:
|
| 492 |
+
sf.write(tmp.name, final_wav, sr)
|
| 493 |
+
output_path = tmp.name
|
| 494 |
+
|
| 495 |
+
process_time = time.time() - start_time
|
| 496 |
+
backend_info = f" (Backend: {'LMDeploy 🚀' if using_lmdeploy else 'Standard 📦'})"
|
| 497 |
+
speed_info = f", Tốc độ: {len(final_wav)/sr/process_time:.2f}x realtime" if process_time > 0 else ""
|
| 498 |
+
|
| 499 |
+
yield output_path, f"✅ Hoàn tất! (Thời gian: {process_time:.2f}s{speed_info}){backend_info}"
|
| 500 |
+
|
| 501 |
+
# Cleanup memory
|
| 502 |
+
if using_lmdeploy and hasattr(tts, 'cleanup_memory'):
|
| 503 |
+
tts.cleanup_memory()
|
| 504 |
+
cleanup_gpu_memory()
|
| 505 |
+
|
| 506 |
+
except torch.cuda.OutOfMemoryError as e:
|
| 507 |
+
cleanup_gpu_memory()
|
| 508 |
+
yield None, (
|
| 509 |
+
f"❌ GPU hết VRAM! Hãy thử:\n"
|
| 510 |
+
f"• Giảm Max Batch Size (hiện tại: {tts.max_batch_size if hasattr(tts, 'max_batch_size') else 'N/A'})\n"
|
| 511 |
+
f"• Giảm độ dài văn bản\n\n"
|
| 512 |
+
f"Chi tiết: {str(e)}"
|
| 513 |
+
)
|
| 514 |
+
return
|
| 515 |
+
|
| 516 |
+
except Exception as e:
|
| 517 |
+
import traceback
|
| 518 |
+
traceback.print_exc()
|
| 519 |
+
cleanup_gpu_memory()
|
| 520 |
+
yield None, f"❌ Lỗi Standard Mode: {str(e)}"
|
| 521 |
+
return
|
| 522 |
+
|
| 523 |
+
# === STREAMING MODE ===
|
| 524 |
+
else:
|
| 525 |
+
sr = 24000
|
| 526 |
+
crossfade_samples = int(sr * 0.03)
|
| 527 |
+
audio_queue = queue.Queue(maxsize=100)
|
| 528 |
+
PRE_BUFFER_SIZE = 3
|
| 529 |
+
|
| 530 |
+
end_event = threading.Event()
|
| 531 |
+
error_event = threading.Event()
|
| 532 |
+
error_msg = ""
|
| 533 |
+
|
| 534 |
+
def producer_thread():
|
| 535 |
+
nonlocal error_msg
|
| 536 |
+
try:
|
| 537 |
+
previous_tail = None
|
| 538 |
+
|
| 539 |
+
for i, chunk_text in enumerate(text_chunks):
|
| 540 |
+
stream_gen = tts.infer_stream(chunk_text, ref_codes, ref_text_raw)
|
| 541 |
+
|
| 542 |
+
for part_idx, audio_part in enumerate(stream_gen):
|
| 543 |
+
if audio_part is None or len(audio_part) == 0:
|
| 544 |
+
continue
|
| 545 |
+
|
| 546 |
+
if previous_tail is not None and len(previous_tail) > 0:
|
| 547 |
+
overlap = min(len(previous_tail), len(audio_part), crossfade_samples)
|
| 548 |
+
if overlap > 0:
|
| 549 |
+
fade_out = np.linspace(1.0, 0.0, overlap, dtype=np.float32)
|
| 550 |
+
fade_in = np.linspace(0.0, 1.0, overlap, dtype=np.float32)
|
| 551 |
+
|
| 552 |
+
blended = (audio_part[:overlap] * fade_in +
|
| 553 |
+
previous_tail[-overlap:] * fade_out)
|
| 554 |
+
|
| 555 |
+
processed = np.concatenate([
|
| 556 |
+
previous_tail[:-overlap] if len(previous_tail) > overlap else np.array([]),
|
| 557 |
+
blended,
|
| 558 |
+
audio_part[overlap:]
|
| 559 |
+
])
|
| 560 |
+
else:
|
| 561 |
+
processed = np.concatenate([previous_tail, audio_part])
|
| 562 |
+
|
| 563 |
+
tail_size = min(crossfade_samples, len(processed))
|
| 564 |
+
previous_tail = processed[-tail_size:].copy()
|
| 565 |
+
output_chunk = processed[:-tail_size] if len(processed) > tail_size else processed
|
| 566 |
+
else:
|
| 567 |
+
tail_size = min(crossfade_samples, len(audio_part))
|
| 568 |
+
previous_tail = audio_part[-tail_size:].copy()
|
| 569 |
+
output_chunk = audio_part[:-tail_size] if len(audio_part) > tail_size else audio_part
|
| 570 |
+
|
| 571 |
+
if len(output_chunk) > 0:
|
| 572 |
+
audio_queue.put((sr, output_chunk))
|
| 573 |
+
|
| 574 |
+
if previous_tail is not None and len(previous_tail) > 0:
|
| 575 |
+
audio_queue.put((sr, previous_tail))
|
| 576 |
+
|
| 577 |
+
except Exception as e:
|
| 578 |
+
import traceback
|
| 579 |
+
traceback.print_exc()
|
| 580 |
+
error_msg = str(e)
|
| 581 |
+
error_event.set()
|
| 582 |
+
finally:
|
| 583 |
+
end_event.set()
|
| 584 |
+
audio_queue.put(None)
|
| 585 |
+
|
| 586 |
+
threading.Thread(target=producer_thread, daemon=True).start()
|
| 587 |
+
|
| 588 |
+
yield (sr, np.zeros(int(sr * 0.05))), "📄 Đang buffering..."
|
| 589 |
+
|
| 590 |
+
pre_buffer = []
|
| 591 |
+
while len(pre_buffer) < PRE_BUFFER_SIZE:
|
| 592 |
+
try:
|
| 593 |
+
item = audio_queue.get(timeout=5.0)
|
| 594 |
+
if item is None:
|
| 595 |
+
break
|
| 596 |
+
pre_buffer.append(item)
|
| 597 |
+
except queue.Empty:
|
| 598 |
+
if error_event.is_set():
|
| 599 |
+
yield None, f"❌ Lỗi: {error_msg}"
|
| 600 |
+
return
|
| 601 |
+
break
|
| 602 |
+
|
| 603 |
+
full_audio_buffer = []
|
| 604 |
+
backend_info = "🚀 LMDeploy" if using_lmdeploy else "📦 Standard"
|
| 605 |
+
for sr, audio_data in pre_buffer:
|
| 606 |
+
full_audio_buffer.append(audio_data)
|
| 607 |
+
yield (sr, audio_data), f"🔊 Đang phát ({backend_info})..."
|
| 608 |
+
|
| 609 |
+
while True:
|
| 610 |
+
try:
|
| 611 |
+
item = audio_queue.get(timeout=0.05)
|
| 612 |
+
if item is None:
|
| 613 |
+
break
|
| 614 |
+
sr, audio_data = item
|
| 615 |
+
full_audio_buffer.append(audio_data)
|
| 616 |
+
yield (sr, audio_data), f"🔊 Đang phát ({backend_info})..."
|
| 617 |
+
except queue.Empty:
|
| 618 |
+
if error_event.is_set():
|
| 619 |
+
yield None, f"❌ Lỗi: {error_msg}"
|
| 620 |
+
break
|
| 621 |
+
if end_event.is_set() and audio_queue.empty():
|
| 622 |
+
break
|
| 623 |
+
continue
|
| 624 |
+
|
| 625 |
+
if full_audio_buffer:
|
| 626 |
+
final_wav = np.concatenate(full_audio_buffer)
|
| 627 |
+
with tempfile.NamedTemporaryFile(delete=False, suffix=".wav") as tmp:
|
| 628 |
+
sf.write(tmp.name, final_wav, sr)
|
| 629 |
+
yield tmp.name, f"✅ Hoàn tất Streaming! ({backend_info})"
|
| 630 |
+
|
| 631 |
+
# Cleanup memory
|
| 632 |
+
if using_lmdeploy and hasattr(tts, 'cleanup_memory'):
|
| 633 |
+
tts.cleanup_memory()
|
| 634 |
+
cleanup_gpu_memory()
|
| 635 |
+
|
| 636 |
+
|
| 637 |
+
# --- 4. UI SETUP ---
|
| 638 |
+
theme = gr.themes.Soft(
|
| 639 |
+
primary_hue="indigo",
|
| 640 |
+
secondary_hue="cyan",
|
| 641 |
+
neutral_hue="slate",
|
| 642 |
+
font=[gr.themes.GoogleFont('Inter'), 'ui-sans-serif', 'system-ui'],
|
| 643 |
+
).set(
|
| 644 |
+
button_primary_background_fill="linear-gradient(90deg, #6366f1 0%, #0ea5e9 100%)",
|
| 645 |
+
button_primary_background_fill_hover="linear-gradient(90deg, #4f46e5 0%, #0284c7 100%)",
|
| 646 |
+
)
|
| 647 |
+
|
| 648 |
+
css = """
|
| 649 |
+
.container { max-width: 1400px; margin: auto; }
|
| 650 |
+
.header-box {
|
| 651 |
+
text-align: center;
|
| 652 |
+
margin-bottom: 25px;
|
| 653 |
+
padding: 25px;
|
| 654 |
+
background: linear-gradient(135deg, #0f172a 0%, #1e293b 100%);
|
| 655 |
+
border-radius: 12px;
|
| 656 |
+
color: white !important;
|
| 657 |
+
}
|
| 658 |
+
.header-title {
|
| 659 |
+
font-size: 2.5rem;
|
| 660 |
+
font-weight: 800;
|
| 661 |
+
color: white !important;
|
| 662 |
+
}
|
| 663 |
+
.gradient-text {
|
| 664 |
+
background: -webkit-linear-gradient(45deg, #60A5FA, #22D3EE);
|
| 665 |
+
-webkit-background-clip: text;
|
| 666 |
+
-webkit-text-fill-color: transparent;
|
| 667 |
+
}
|
| 668 |
+
.header-icon {
|
| 669 |
+
color: white;
|
| 670 |
+
}
|
| 671 |
+
.status-box {
|
| 672 |
+
font-weight: bold;
|
| 673 |
+
text-align: center;
|
| 674 |
+
border: none;
|
| 675 |
+
background: transparent;
|
| 676 |
+
}
|
| 677 |
+
.model-card-content {
|
| 678 |
+
display: flex;
|
| 679 |
+
flex-wrap: wrap;
|
| 680 |
+
justify-content: center;
|
| 681 |
+
align-items: center;
|
| 682 |
+
gap: 15px;
|
| 683 |
+
font-size: 0.9rem;
|
| 684 |
+
text-align: center;
|
| 685 |
+
color: white !important;
|
| 686 |
+
}
|
| 687 |
+
.model-card-item {
|
| 688 |
+
display: flex;
|
| 689 |
+
align-items: center;
|
| 690 |
+
justify-content: center;
|
| 691 |
+
gap: 6px;
|
| 692 |
+
color: white !important;
|
| 693 |
+
}
|
| 694 |
+
.model-card-item strong {
|
| 695 |
+
color: white !important;
|
| 696 |
+
}
|
| 697 |
+
.model-card-item span {
|
| 698 |
+
color: white !important;
|
| 699 |
+
}
|
| 700 |
+
.model-card-link {
|
| 701 |
+
color: #60A5FA;
|
| 702 |
+
text-decoration: none;
|
| 703 |
+
font-weight: 500;
|
| 704 |
+
transition: color 0.2s;
|
| 705 |
+
}
|
| 706 |
+
.model-card-link:hover {
|
| 707 |
+
color: #22D3EE;
|
| 708 |
+
text-decoration: underline;
|
| 709 |
+
}
|
| 710 |
+
.warning-banner {
|
| 711 |
+
background-color: #fffbeb;
|
| 712 |
+
border: 1px solid #fef3c7;
|
| 713 |
+
border-radius: 12px;
|
| 714 |
+
padding: 16px;
|
| 715 |
+
margin-bottom: 20px;
|
| 716 |
+
}
|
| 717 |
+
.warning-banner-title {
|
| 718 |
+
color: #92400e;
|
| 719 |
+
font-weight: 700;
|
| 720 |
+
font-size: 1.1rem;
|
| 721 |
+
display: flex;
|
| 722 |
+
align-items: center;
|
| 723 |
+
gap: 8px;
|
| 724 |
+
margin-bottom: 12px;
|
| 725 |
+
}
|
| 726 |
+
.warning-banner-grid {
|
| 727 |
+
display: flex;
|
| 728 |
+
gap: 15px;
|
| 729 |
+
flex-wrap: wrap;
|
| 730 |
+
}
|
| 731 |
+
.warning-banner-item {
|
| 732 |
+
flex: 1;
|
| 733 |
+
min-width: 240px;
|
| 734 |
+
background: #fef3c7;
|
| 735 |
+
padding: 12px;
|
| 736 |
+
border-radius: 8px;
|
| 737 |
+
border: 1px solid #fde68a;
|
| 738 |
+
}
|
| 739 |
+
.warning-banner-item strong {
|
| 740 |
+
color: #b45309;
|
| 741 |
+
display: block;
|
| 742 |
+
margin-bottom: 4px;
|
| 743 |
+
font-size: 0.95rem;
|
| 744 |
+
}
|
| 745 |
+
.warning-banner-content {
|
| 746 |
+
color: #78350f;
|
| 747 |
+
font-size: 0.9rem;
|
| 748 |
+
line-height: 1.5;
|
| 749 |
+
}
|
| 750 |
+
.warning-banner-content b {
|
| 751 |
+
color: #451a03;
|
| 752 |
+
background: rgba(251, 191, 36, 0.2);
|
| 753 |
+
padding: 1px 4px;
|
| 754 |
+
border-radius: 4px;
|
| 755 |
+
}
|
| 756 |
+
"""
|
| 757 |
+
|
| 758 |
+
EXAMPLES_LIST = [
|
| 759 |
+
["Về miền Tây không chỉ để ngắm nhìn sông nước hữu tình, mà còn để cảm nhận tấm chân tình của người dân nơi đây.", "Vĩnh (nam miền Nam)"],
|
| 760 |
+
["Hà Nội những ngày vào thu mang một vẻ đẹp trầm mặc và cổ kính đến lạ thường.", "Bình (nam miền Bắc)"],
|
| 761 |
+
]
|
| 762 |
+
|
| 763 |
+
with gr.Blocks(theme=theme, css=css, title="VieNeu-TTS") as demo:
|
| 764 |
+
with gr.Column(elem_classes="container"):
|
| 765 |
+
gr.HTML("""
|
| 766 |
+
<div class="header-box">
|
| 767 |
+
<h1 class="header-title">
|
| 768 |
+
<span class="header-icon">🦜</span>
|
| 769 |
+
<span class="gradient-text">VieNeu-TTS Studio</span>
|
| 770 |
+
</h1>
|
| 771 |
+
<div class="model-card-content">
|
| 772 |
+
<div class="model-card-item">
|
| 773 |
+
<strong>Models:</strong>
|
| 774 |
+
<a href="https://huggingface.co/pnnbao-ump/VieNeu-TTS" target="_blank" class="model-card-link">VieNeu-TTS</a>
|
| 775 |
+
<span>•</span>
|
| 776 |
+
<a href="https://huggingface.co/pnnbao-ump/VieNeu-TTS-0.3B" target="_blank" class="model-card-link">VieNeu-TTS-0.3B</a>
|
| 777 |
+
</div>
|
| 778 |
+
<div class="model-card-item">
|
| 779 |
+
<strong>Repository:</strong>
|
| 780 |
+
<a href="https://github.com/pnnbao97/VieNeu-TTS" target="_blank" class="model-card-link">GitHub</a>
|
| 781 |
+
</div>
|
| 782 |
+
<div class="model-card-item">
|
| 783 |
+
<strong>Tác giả:</strong>
|
| 784 |
+
<a href="https://www.facebook.com/bao.phamnguyenngoc.5" target="_blank" class="model-card-link">Phạm Nguyễn Ngọc Bảo</a>
|
| 785 |
+
</div>
|
| 786 |
+
</div>
|
| 787 |
+
</div>
|
| 788 |
+
""")
|
| 789 |
+
|
| 790 |
+
# --- CONFIGURATION ---
|
| 791 |
+
with gr.Group():
|
| 792 |
+
with gr.Row():
|
| 793 |
+
backbone_select = gr.Dropdown(
|
| 794 |
+
list(BACKBONE_CONFIGS.keys()),
|
| 795 |
+
value="VieNeu-TTS (GPU)",
|
| 796 |
+
label="🦜 Backbone"
|
| 797 |
+
)
|
| 798 |
+
codec_select = gr.Dropdown(list(CODEC_CONFIGS.keys()), value="NeuCodec (Distill)", label="🎵 Codec")
|
| 799 |
+
device_choice = gr.Radio(get_available_devices(), value="Auto", label="🖥️ Device")
|
| 800 |
+
|
| 801 |
+
with gr.Row():
|
| 802 |
+
use_lmdeploy_cb = gr.Checkbox(
|
| 803 |
+
value=True,
|
| 804 |
+
label="🚀 Optimize with LMDeploy (Khuyên dùng cho NVIDIA GPU)",
|
| 805 |
+
info="Tick nếu bạn dùng GPU để tăng tốc độ tổng hợp đáng kể."
|
| 806 |
+
)
|
| 807 |
+
|
| 808 |
+
gr.HTML("""
|
| 809 |
+
<div class="warning-banner">
|
| 810 |
+
<div class="warning-banner-title">
|
| 811 |
+
🦜 Gợi ý tối ưu hiệu năng
|
| 812 |
+
</div>
|
| 813 |
+
<div class="warning-banner-grid">
|
| 814 |
+
<div class="warning-banner-item">
|
| 815 |
+
<strong>🐢 Hệ máy CPU</strong>
|
| 816 |
+
<div class="warning-banner-content">
|
| 817 |
+
Sử dụng <b>VieNeu-TTS-0.3B-q4-gguf</b> để đạt tốc độ xử lý nhanh nhất. Nếu ưu tiên độ chính xác thì dùng <b>VieNeu-TTS-0.3B-q8-gguf</b>.
|
| 818 |
+
</div>
|
| 819 |
+
</div>
|
| 820 |
+
<div class="warning-banner-item">
|
| 821 |
+
<strong>🐆 Hệ máy GPU</strong>
|
| 822 |
+
<div class="warning-banner-content">
|
| 823 |
+
Chọn <b>VieNeu-TTS-0.3B (GPU)</b> để x2 tốc độ (độ chính xác ~95% bản gốc).
|
| 824 |
+
</div>
|
| 825 |
+
</div>
|
| 826 |
+
</div>
|
| 827 |
+
</div>
|
| 828 |
+
""")
|
| 829 |
+
|
| 830 |
+
btn_load = gr.Button("🔄 Tải Model", variant="primary")
|
| 831 |
+
model_status = gr.Markdown("⏳ Chưa tải model.")
|
| 832 |
+
|
| 833 |
+
with gr.Row(elem_classes="container"):
|
| 834 |
+
# --- INPUT ---
|
| 835 |
+
with gr.Column(scale=3):
|
| 836 |
+
text_input = gr.Textbox(
|
| 837 |
+
label=f"Văn bản",
|
| 838 |
+
lines=4,
|
| 839 |
+
value="Hà Nội, trái tim của Việt Nam, là một thành phố ngàn năm văn hiến với bề dày lịch sử và văn hóa độc đáo. Bước chân trên những con phố cổ kính quanh Hồ Hoàn Kiếm, du khách như được du hành ngược thời gian, chiêm ngưỡng kiến trúc Pháp cổ điển hòa quyện với nét kiến trúc truyền thống Việt Nam. Mỗi con phố trong khu phố cổ mang một tên gọi đặc trưng, phản ánh nghề thủ công truyền thống từng thịnh hành nơi đây như phố Hàng Bạc, Hàng Đào, Hàng Mã. Ẩm thực Hà Nội cũng là một điểm nhấn đặc biệt, từ tô phở nóng hổi buổi sáng, bún chả thơm lừng trưa hè, đến chè Thái ngọt ngào chiều thu. Những món ăn dân dã này đã trở thành biểu tượng của văn hóa ẩm thực Việt, được cả thế giới yêu mến. Người Hà Nội nổi tiếng với tính cách hiền hòa, lịch thiệp nhưng cũng rất cầu toàn trong từng chi tiết nhỏ, từ cách pha trà sen cho đến cách chọn hoa sen tây để thưởng trà.",
|
| 840 |
+
)
|
| 841 |
+
|
| 842 |
+
with gr.Tabs() as tabs:
|
| 843 |
+
with gr.TabItem("👤 Preset", id="preset_mode") as tab_preset:
|
| 844 |
+
initial_voices = get_voice_options("VieNeu-TTS (GPU)")
|
| 845 |
+
default_voice = initial_voices[0] if initial_voices else None
|
| 846 |
+
voice_select = gr.Dropdown(initial_voices, value=default_voice, label="Giọng mẫu")
|
| 847 |
+
|
| 848 |
+
with gr.TabItem("🦜 Voice Cloning", id="custom_mode") as tab_custom:
|
| 849 |
+
custom_audio = gr.Audio(label="Audio giọng mẫu (3-5 giây) (.wav)", type="filepath")
|
| 850 |
+
custom_text = gr.Textbox(label="Nội dung audio mẫu - vui lòng gõ đúng nội dung của audio mẫu - kể cả dấu câu vì model rất nhạy cảm với dấu câu (.,?!)")
|
| 851 |
+
gr.Examples(
|
| 852 |
+
examples=[
|
| 853 |
+
[os.path.join("examples", "audio_ref", "example.wav"), "Ví dụ 2. Tính trung bình của dãy số."],
|
| 854 |
+
[os.path.join("examples", "audio_ref", "example_2.wav"), "Trên thực tế, các nghi ngờ đã bắt đầu xuất hiện."]
|
| 855 |
+
],
|
| 856 |
+
inputs=[custom_audio, custom_text],
|
| 857 |
+
label="Ví dụ mẫu để thử nghiệm clone giọng"
|
| 858 |
+
)
|
| 859 |
+
|
| 860 |
+
|
| 861 |
+
generation_mode = gr.Radio(
|
| 862 |
+
["Standard (Một lần)"],
|
| 863 |
+
value="Standard (Một lần)",
|
| 864 |
+
label="Chế độ sinh"
|
| 865 |
+
)
|
| 866 |
+
with gr.Row():
|
| 867 |
+
use_batch = gr.Checkbox(
|
| 868 |
+
value=True,
|
| 869 |
+
label="⚡ Batch Processing",
|
| 870 |
+
info="Xử lý nhiều đoạn cùng lúc (chỉ áp dụng khi sử dụng GPU và đã cài đặt LMDeploy)"
|
| 871 |
+
)
|
| 872 |
+
max_batch_size_run = gr.Slider(
|
| 873 |
+
minimum=1,
|
| 874 |
+
maximum=16,
|
| 875 |
+
value=4,
|
| 876 |
+
step=1,
|
| 877 |
+
label="📊 Batch Size (Generation)",
|
| 878 |
+
info="Số lượng đoạn văn bản xử lý cùng lúc. Giá trị cao = nhanh hơn nhưng tốn VRAM hơn. Giảm xuống nếu gặp lỗi Out of Memory."
|
| 879 |
+
)
|
| 880 |
+
|
| 881 |
+
# State to track current mode (replaces unreliable Textbox/Tabs input)
|
| 882 |
+
current_mode_state = gr.State("preset_mode")
|
| 883 |
+
|
| 884 |
+
with gr.Row():
|
| 885 |
+
btn_generate = gr.Button("🎵 Bắt đầu", variant="primary", scale=2, interactive=False)
|
| 886 |
+
btn_stop = gr.Button("⏹️ Dừng", variant="stop", scale=1, interactive=False)
|
| 887 |
+
|
| 888 |
+
# --- OUTPUT ---
|
| 889 |
+
with gr.Column(scale=2):
|
| 890 |
+
audio_output = gr.Audio(
|
| 891 |
+
label="Kết quả",
|
| 892 |
+
type="filepath",
|
| 893 |
+
autoplay=True
|
| 894 |
+
)
|
| 895 |
+
status_output = gr.Textbox(label="Trạng thái", elem_classes="status-box")
|
| 896 |
+
gr.Markdown("<div style='text-align: center; color: #64748b; font-size: 0.8rem;'>🔒 Audio được đóng dấu bản quyền ẩn (Watermarker) để bảo mật và định danh AI.</div>")
|
| 897 |
+
|
| 898 |
+
# # --- EVENT HANDLERS ---
|
| 899 |
+
# def update_info(backbone: str) -> str:
|
| 900 |
+
# return f"Streaming: {'✅' if BACKBONE_CONFIGS[backbone]['supports_streaming'] else '❌'}"
|
| 901 |
+
|
| 902 |
+
# backbone_select.change(update_info, backbone_select, model_status)
|
| 903 |
+
backbone_select.change(update_voice_dropdown, [backbone_select, voice_select], voice_select)
|
| 904 |
+
|
| 905 |
+
# Handler to show/hide Voice Cloning tab
|
| 906 |
+
def on_codec_change(codec: str):
|
| 907 |
+
is_onnx = "onnx" in codec.lower()
|
| 908 |
+
# If switching to ONNX and we are on custom mode, switch back to preset
|
| 909 |
+
return gr.update(visible=not is_onnx), gr.update(selected="preset_mode" if is_onnx else None)
|
| 910 |
+
|
| 911 |
+
codec_select.change(
|
| 912 |
+
on_codec_change,
|
| 913 |
+
inputs=[codec_select],
|
| 914 |
+
outputs=[tab_custom, tabs]
|
| 915 |
+
)
|
| 916 |
+
|
| 917 |
+
# Bind tab events to update state
|
| 918 |
+
tab_preset.select(lambda: "preset_mode", outputs=current_mode_state)
|
| 919 |
+
tab_custom.select(lambda: "custom_mode", outputs=current_mode_state)
|
| 920 |
+
|
| 921 |
+
btn_load.click(
|
| 922 |
+
fn=load_model,
|
| 923 |
+
inputs=[backbone_select, codec_select, device_choice, use_lmdeploy_cb],
|
| 924 |
+
outputs=[model_status, btn_generate, btn_load, btn_stop]
|
| 925 |
+
)
|
| 926 |
+
|
| 927 |
+
generate_event = btn_generate.click(
|
| 928 |
+
fn=synthesize_speech,
|
| 929 |
+
inputs=[text_input, voice_select, custom_audio, custom_text, current_mode_state, generation_mode, use_batch, max_batch_size_run],
|
| 930 |
+
outputs=[audio_output, status_output]
|
| 931 |
+
)
|
| 932 |
+
|
| 933 |
+
# When generation starts, enable stop button
|
| 934 |
+
btn_generate.click(lambda: gr.update(interactive=True), outputs=btn_stop)
|
| 935 |
+
# When generation ends/stops, disable stop button
|
| 936 |
+
generate_event.then(lambda: gr.update(interactive=False), outputs=btn_stop)
|
| 937 |
+
|
| 938 |
+
btn_stop.click(fn=None, cancels=[generate_event])
|
| 939 |
+
btn_stop.click(lambda: (None, "⏹️ Đã dừng tạo giọng nói."), outputs=[audio_output, status_output])
|
| 940 |
+
btn_stop.click(lambda: gr.update(interactive=False), outputs=btn_stop)
|
| 941 |
+
|
| 942 |
+
# Persistence: Restore UI state on load
|
| 943 |
+
demo.load(
|
| 944 |
+
fn=restore_ui_state,
|
| 945 |
+
outputs=[model_status, btn_generate, btn_stop]
|
| 946 |
+
)
|
| 947 |
+
|
| 948 |
+
if __name__ == "__main__":
|
| 949 |
+
# Cho phép override từ biến môi trường (hữu ích cho Docker)
|
| 950 |
+
server_name = os.getenv("GRADIO_SERVER_NAME", "127.0.0.1")
|
| 951 |
+
server_port = int(os.getenv("GRADIO_SERVER_PORT", "7860"))
|
| 952 |
+
|
| 953 |
+
# Check running in Colab
|
| 954 |
+
is_on_colab = os.getenv("COLAB_RELEASE_TAG") is not None
|
| 955 |
+
|
| 956 |
+
# Default:
|
| 957 |
+
# - Colab: share=True (convenient)
|
| 958 |
+
# - Docker/local: share=False (safe)
|
| 959 |
+
share = env_bool("GRADIO_SHARE", default=is_on_colab)
|
| 960 |
+
#
|
| 961 |
+
# If server_name is "0.0.0.0" and GRADIO_SHARE is not set, disable sharing
|
| 962 |
+
if server_name == "0.0.0.0" and os.getenv("GRADIO_SHARE") is None:
|
| 963 |
+
share = False
|
| 964 |
+
|
| 965 |
+
demo.queue().launch(server_name=server_name, server_port=server_port, share=share)
|
main.py
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from vieneu_tts import VieNeuTTS
|
| 2 |
+
import soundfile as sf
|
| 3 |
+
import torch
|
| 4 |
+
import os
|
| 5 |
+
|
| 6 |
+
device = "cpu"
|
| 7 |
+
|
| 8 |
+
input_texts = [
|
| 9 |
+
"Các khóa học trực tuyến đang giúp học sinh tiếp cận kiến thức mọi lúc mọi nơi. Giáo viên sử dụng video, bài tập tương tác và thảo luận trực tuyến để nâng cao hiệu quả học tập.",
|
| 10 |
+
|
| 11 |
+
"Các nghiên cứu về bệnh Alzheimer cho thấy tác dụng tích cực của các bài tập trí não và chế độ dinh dưỡng lành mạnh, giúp giảm tốc độ suy giảm trí nhớ ở người cao tuổi.",
|
| 12 |
+
|
| 13 |
+
"Một tiểu thuyết trinh thám hiện đại dẫn dắt độc giả qua những tình tiết phức tạp, bí ẩn, kết hợp yếu tố tâm lý sâu sắc khiến người đọc luôn hồi hộp theo dõi diễn biến câu chuyện.",
|
| 14 |
+
|
| 15 |
+
"Các nhà khoa học nghiên cứu gen người phát hiện những đột biến mới liên quan đến bệnh di truyền. Điều này giúp nâng cao khả năng chẩn đoán và điều trị.",
|
| 16 |
+
]
|
| 17 |
+
|
| 18 |
+
output_dir = "./output_audio"
|
| 19 |
+
os.makedirs(output_dir, exist_ok=True)
|
| 20 |
+
|
| 21 |
+
def main(backbone="pnnbao-ump/VieNeu-TTS-q4-gguf", codec="neuphonic/neucodec-onnx-decoder"):
|
| 22 |
+
"""
|
| 23 |
+
In the sample directory, there are wav files and txt files with matching names.
|
| 24 |
+
These are pre-prepared reference files for testing with Vietnamese names:
|
| 25 |
+
- Bình (nam miền Bắc) - Male, North accent
|
| 26 |
+
- Tuyên (nam miền Bắc) - Male, North accent
|
| 27 |
+
- Nguyên (nam miền Nam) - Male, South accent
|
| 28 |
+
- Sơn (nam miền Nam) - Male, South accent
|
| 29 |
+
- Vĩnh (nam miền Nam) - Male, South accent
|
| 30 |
+
- Hương (nữ miền Bắc) - Female, North accent
|
| 31 |
+
- Ly (nữ miền Bắc) - Female, North accent
|
| 32 |
+
- Ngọc (nữ miền Bắc) - Female, North accent
|
| 33 |
+
- Đoan (nữ miền Nam) - Female, South accent
|
| 34 |
+
- Dung (nữ miền Nam) - Female, South accent
|
| 35 |
+
|
| 36 |
+
Note: The model can clone any voice you provide (with corresponding text).
|
| 37 |
+
However, quality may not match the sample files. For best results, finetune
|
| 38 |
+
the model on your target voice. See finetune guide at:
|
| 39 |
+
https://github.com/pnnbao-ump/VieNeuTTS/blob/main/finetune.ipynb
|
| 40 |
+
"""
|
| 41 |
+
# Male voice (South accent)
|
| 42 |
+
ref_audio_path = "./sample/Vĩnh (nam miền Nam).wav"
|
| 43 |
+
ref_text_path = "./sample/Vĩnh (nam miền Nam).txt"
|
| 44 |
+
ref_codes_path = "./sample/Vĩnh (nam miền Nam).pt"
|
| 45 |
+
|
| 46 |
+
# Female voice (South accent) - uncomment to use
|
| 47 |
+
# ref_audio_path = "./sample/Đoan (nữ miền Nam).wav"
|
| 48 |
+
# ref_text_path = "./sample/Đoan (nữ miền Nam).txt"
|
| 49 |
+
|
| 50 |
+
ref_text_raw = open(ref_text_path, "r", encoding="utf-8").read()
|
| 51 |
+
|
| 52 |
+
if not ref_audio_path or not ref_text_raw:
|
| 53 |
+
print("No reference audio or text provided.")
|
| 54 |
+
return None
|
| 55 |
+
|
| 56 |
+
# Initialize VieNeuTTS-1000h
|
| 57 |
+
tts = VieNeuTTS(
|
| 58 |
+
backbone_repo=backbone,
|
| 59 |
+
backbone_device=device,
|
| 60 |
+
codec_repo=codec,
|
| 61 |
+
codec_device=device
|
| 62 |
+
)
|
| 63 |
+
|
| 64 |
+
if codec == "neuphonic/neucodec-onnx-decoder":
|
| 65 |
+
print("Load reference codes...")
|
| 66 |
+
ref_codes = torch.load(ref_codes_path, map_location=device)
|
| 67 |
+
else:
|
| 68 |
+
print("Encoding reference audio...")
|
| 69 |
+
ref_codes = tts.encode_reference(ref_audio_path)
|
| 70 |
+
|
| 71 |
+
# Generate speech for all input texts
|
| 72 |
+
for i, text in enumerate(input_texts, 1):
|
| 73 |
+
print(f"Generating audio {i}/{len(input_texts)}: {text[:50]}...")
|
| 74 |
+
wav = tts.infer(text, ref_codes, ref_text_raw)
|
| 75 |
+
output_path = os.path.join(output_dir, f"output_{i}.wav")
|
| 76 |
+
sf.write(output_path, wav, 24000)
|
| 77 |
+
print(f"✓ Saved to {output_path}")
|
| 78 |
+
|
| 79 |
+
if __name__ == "__main__":
|
| 80 |
+
main()
|
pyproject.toml
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[tool.uv]
|
| 2 |
+
index-strategy = "unsafe-best-match"
|
| 3 |
+
required-environments = [
|
| 4 |
+
"sys_platform == 'win32' and platform_machine == 'AMD64'",
|
| 5 |
+
"sys_platform == 'linux' and platform_machine == 'x86_64'",
|
| 6 |
+
"sys_platform == 'darwin' and platform_machine == 'arm64'",
|
| 7 |
+
]
|
| 8 |
+
override-dependencies = [
|
| 9 |
+
"nvidia-nccl-cu12; sys_platform == 'linux'",
|
| 10 |
+
]
|
| 11 |
+
|
| 12 |
+
[[tool.uv.index]]
|
| 13 |
+
name = "pytorch"
|
| 14 |
+
url = "https://download.pytorch.org/whl/cu128"
|
| 15 |
+
explicit = true
|
| 16 |
+
|
| 17 |
+
[[tool.uv.index]]
|
| 18 |
+
name = "pypi"
|
| 19 |
+
url = "https://pypi.org/simple"
|
| 20 |
+
|
| 21 |
+
[project]
|
| 22 |
+
name = "VieNeu-TTS"
|
| 23 |
+
version = "0.1.0"
|
| 24 |
+
description = "Advanced on-device Vietnamese TTS with instant voice cloning"
|
| 25 |
+
readme = "README.md"
|
| 26 |
+
requires-python = "==3.12.*"
|
| 27 |
+
dependencies = [
|
| 28 |
+
"phonemizer>=3.3.0",
|
| 29 |
+
"neucodec>=0.0.4",
|
| 30 |
+
"librosa>=0.11.0",
|
| 31 |
+
"gradio>=5.49.1",
|
| 32 |
+
"onnxruntime>=1.23.2",
|
| 33 |
+
"datasets>=3.2.0",
|
| 34 |
+
"lmdeploy; sys_platform != 'darwin'",
|
| 35 |
+
"triton-windows; sys_platform == 'win32'",
|
| 36 |
+
"triton; sys_platform == 'linux'",
|
| 37 |
+
"transformers; sys_platform == 'darwin'",
|
| 38 |
+
"accelerate; sys_platform == 'darwin'",
|
| 39 |
+
"torch",
|
| 40 |
+
"torchvision",
|
| 41 |
+
"torchaudio",
|
| 42 |
+
"perth>=0.2.0",
|
| 43 |
+
"llama-cpp-python==0.3.16",
|
| 44 |
+
]
|
| 45 |
+
|
| 46 |
+
[tool.uv.sources]
|
| 47 |
+
|
| 48 |
+
torch = [
|
| 49 |
+
{ index = "pytorch", marker = "sys_platform != 'darwin'" },
|
| 50 |
+
{ index = "pypi", marker = "sys_platform == 'darwin'" }
|
| 51 |
+
]
|
| 52 |
+
torchvision = [
|
| 53 |
+
{ index = "pytorch", marker = "sys_platform != 'darwin'" },
|
| 54 |
+
{ index = "pypi", marker = "sys_platform == 'darwin'" }
|
| 55 |
+
]
|
| 56 |
+
torchaudio = [
|
| 57 |
+
{ index = "pytorch", marker = "sys_platform != 'darwin'" },
|
| 58 |
+
{ index = "pypi", marker = "sys_platform == 'darwin'" }
|
| 59 |
+
]
|
| 60 |
+
|
| 61 |
+
lmdeploy = [
|
| 62 |
+
{ url = "https://github.com/InternLM/lmdeploy/releases/download/v0.11.0/lmdeploy-0.11.0+cu128-cp312-cp312-win_amd64.whl", marker = "sys_platform == 'win32' and python_version == '3.12'" },
|
| 63 |
+
{ url = "https://github.com/InternLM/lmdeploy/releases/download/v0.11.0/lmdeploy-0.11.0+cu128-cp312-cp312-manylinux2014_x86_64.whl", marker = "sys_platform == 'linux' and python_version == '3.12'" },
|
| 64 |
+
{ index = "pypi", marker = "sys_platform == 'darwin'" }
|
| 65 |
+
]
|
| 66 |
+
|
| 67 |
+
llama-cpp-python = [
|
| 68 |
+
{ url = "https://github.com/pnnbao97/VieNeu-TTS/releases/download/wheels-v0.3.16/llama_cpp_python-0.3.16-cp312-cp312-win_amd64.whl", marker = "sys_platform == 'win32' and python_version == '3.12'" },
|
| 69 |
+
{ index = "pypi", marker = "sys_platform != 'win32'" }
|
| 70 |
+
]
|
requirements.txt
ADDED
|
@@ -0,0 +1,162 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
accelerate==1.12.0
|
| 2 |
+
addict==2.4.0
|
| 3 |
+
aiofiles==24.1.0
|
| 4 |
+
aiohappyeyeballs==2.6.1
|
| 5 |
+
aiohttp==3.13.1
|
| 6 |
+
aiosignal==1.4.0
|
| 7 |
+
annotated-doc==0.0.3
|
| 8 |
+
annotated-types==0.7.0
|
| 9 |
+
antlr4-python3-runtime==4.9.3
|
| 10 |
+
anyio==4.11.0
|
| 11 |
+
attrs==25.4.0
|
| 12 |
+
audioread==3.1.0
|
| 13 |
+
babel==2.17.0
|
| 14 |
+
blobfile==3.1.0
|
| 15 |
+
brotli==1.1.0
|
| 16 |
+
certifi==2025.10.5
|
| 17 |
+
cffi==2.0.0
|
| 18 |
+
charset-normalizer==3.4.4
|
| 19 |
+
click==8.3.0
|
| 20 |
+
cloudpickle==3.1.2
|
| 21 |
+
colorama==0.4.6
|
| 22 |
+
coloredlogs==15.0.1
|
| 23 |
+
csvw==3.7.0
|
| 24 |
+
datasets==4.3.0
|
| 25 |
+
decorator==5.2.1
|
| 26 |
+
dill==0.4.0
|
| 27 |
+
distro==1.9.0
|
| 28 |
+
dlinfo==2.0.0
|
| 29 |
+
einops==0.8.1
|
| 30 |
+
einx==0.3.0
|
| 31 |
+
fastapi==0.120.2
|
| 32 |
+
ffmpy==0.6.4
|
| 33 |
+
filelock==3.20.0
|
| 34 |
+
fire==0.7.1
|
| 35 |
+
flatbuffers==25.9.23
|
| 36 |
+
frozendict==2.4.6
|
| 37 |
+
frozenlist==1.8.0
|
| 38 |
+
fsspec==2025.9.0
|
| 39 |
+
gradio==5.49.1
|
| 40 |
+
gradio-client==1.13.3
|
| 41 |
+
groovy==0.1.2
|
| 42 |
+
h11==0.16.0
|
| 43 |
+
hf-transfer==0.1.9
|
| 44 |
+
httpcore==1.0.9
|
| 45 |
+
httpx==0.28.1
|
| 46 |
+
huggingface-hub==0.36.0
|
| 47 |
+
humanfriendly==10.0
|
| 48 |
+
hyper-connections==0.2.1
|
| 49 |
+
idna==3.11
|
| 50 |
+
inquirerpy==0.3.4
|
| 51 |
+
isodate==0.7.2
|
| 52 |
+
jinja2==3.1.6
|
| 53 |
+
jiter==0.12.0
|
| 54 |
+
joblib==1.5.2
|
| 55 |
+
jsonschema==4.25.1
|
| 56 |
+
jsonschema-specifications==2025.9.1
|
| 57 |
+
kagglehub==0.3.13
|
| 58 |
+
language-tags==1.2.0
|
| 59 |
+
lazy-loader==0.4
|
| 60 |
+
librosa==0.11.0
|
| 61 |
+
llvmlite==0.45.1
|
| 62 |
+
lmdeploy==0.11.0
|
| 63 |
+
local-attention==1.11.2
|
| 64 |
+
lxml==6.0.2
|
| 65 |
+
markdown-it-py==4.0.0
|
| 66 |
+
markupsafe==3.0.3
|
| 67 |
+
mdurl==0.1.2
|
| 68 |
+
mmengine-lite==0.10.7
|
| 69 |
+
mpmath==1.3.0
|
| 70 |
+
msgpack==1.1.2
|
| 71 |
+
multidict==6.7.0
|
| 72 |
+
multiprocess==0.70.16
|
| 73 |
+
networkx==3.5
|
| 74 |
+
neucodec==0.0.4
|
| 75 |
+
numba==0.62.1
|
| 76 |
+
numpy==2.3.4
|
| 77 |
+
omegaconf==2.3.0
|
| 78 |
+
onnxruntime==1.23.2
|
| 79 |
+
openai==2.12.0
|
| 80 |
+
openai-harmony==0.0.8
|
| 81 |
+
orjson==3.11.4
|
| 82 |
+
packaging==25.0
|
| 83 |
+
pandas==2.3.3
|
| 84 |
+
partial-json-parser==0.2.1.1.post7
|
| 85 |
+
peft==0.14.0
|
| 86 |
+
pfzy==0.3.4
|
| 87 |
+
phonemizer==3.3.0
|
| 88 |
+
pillow==11.3.0
|
| 89 |
+
platformdirs==4.5.0
|
| 90 |
+
pooch==1.8.2
|
| 91 |
+
prometheus-client==0.23.1
|
| 92 |
+
prompt-toolkit==3.0.52
|
| 93 |
+
propcache==0.4.1
|
| 94 |
+
protobuf==6.33.2
|
| 95 |
+
psutil==7.1.2
|
| 96 |
+
pyarrow==22.0.0
|
| 97 |
+
pycparser==2.23
|
| 98 |
+
pycryptodomex==3.23.0
|
| 99 |
+
pydantic==2.11.10
|
| 100 |
+
pydantic-core==2.33.2
|
| 101 |
+
pydub==0.25.1
|
| 102 |
+
pygments==2.19.2
|
| 103 |
+
pyparsing==3.2.5
|
| 104 |
+
pyreadline3==3.5.4
|
| 105 |
+
python-dateutil==2.9.0.post0
|
| 106 |
+
python-multipart==0.0.20
|
| 107 |
+
pytz==2025.2
|
| 108 |
+
pyyaml==6.0.3
|
| 109 |
+
pyzmq==27.1.0
|
| 110 |
+
ray==2.52.1
|
| 111 |
+
rdflib==7.3.0
|
| 112 |
+
referencing==0.37.0
|
| 113 |
+
regex==2025.10.23
|
| 114 |
+
requests==2.32.5
|
| 115 |
+
rfc3986==1.5.0
|
| 116 |
+
rich==14.2.0
|
| 117 |
+
rpds-py==0.28.0
|
| 118 |
+
ruff==0.14.2
|
| 119 |
+
safehttpx==0.1.7
|
| 120 |
+
safetensors==0.6.2
|
| 121 |
+
scikit-learn==1.7.2
|
| 122 |
+
scipy==1.16.2
|
| 123 |
+
segments==2.3.0
|
| 124 |
+
semantic-version==2.10.0
|
| 125 |
+
sentencepiece==0.2.1
|
| 126 |
+
setuptools==80.9.0
|
| 127 |
+
shellingham==1.5.4
|
| 128 |
+
shortuuid==1.0.13
|
| 129 |
+
six==1.17.0
|
| 130 |
+
sniffio==1.3.1
|
| 131 |
+
soundfile==0.13.1
|
| 132 |
+
soxr==1.0.0
|
| 133 |
+
starlette==0.49.1
|
| 134 |
+
sympy==1.14.0
|
| 135 |
+
termcolor==3.2.0
|
| 136 |
+
threadpoolctl==3.6.0
|
| 137 |
+
tiktoken==0.12.0
|
| 138 |
+
tokenizers==0.22.1
|
| 139 |
+
tomlkit==0.13.3
|
| 140 |
+
torch==2.7.1+cu118
|
| 141 |
+
torchao==0.14.1
|
| 142 |
+
torchaudio==2.7.1+cu118
|
| 143 |
+
torchdata==0.11.0
|
| 144 |
+
torchtune==0.6.1
|
| 145 |
+
torchvision==0.22.1+cu118
|
| 146 |
+
tqdm==4.67.1
|
| 147 |
+
transformers==4.57.1
|
| 148 |
+
triton-windows==3.5.1.post22
|
| 149 |
+
typer==0.20.0
|
| 150 |
+
typing-extensions==4.15.0
|
| 151 |
+
typing-inspection==0.4.2
|
| 152 |
+
tzdata==2025.2
|
| 153 |
+
uritemplate==4.2.0
|
| 154 |
+
urllib3==2.5.0
|
| 155 |
+
uvicorn==0.38.0
|
| 156 |
+
vector-quantize-pytorch==1.17.8
|
| 157 |
+
wcwidth==0.2.14
|
| 158 |
+
websockets==15.0.1
|
| 159 |
+
xgrammar==0.1.28
|
| 160 |
+
xxhash==3.6.0
|
| 161 |
+
yapf==0.43.0
|
| 162 |
+
yarl==1.22.0
|
uv.lock
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|