Simonlob commited on
Commit
4a6cee0
·
1 Parent(s): 09e301c
Files changed (9) hide show
  1. .DS_Store +0 -0
  2. CLAUDE.md +96 -0
  3. README.md +6 -7
  4. app.py +124 -0
  5. create_env.py +21 -0
  6. examples.yaml +45 -0
  7. model_config.yaml +38 -0
  8. requirements.txt +2 -0
  9. util.py +97 -0
.DS_Store ADDED
Binary file (6.15 kB). View file
 
CLAUDE.md ADDED
@@ -0,0 +1,96 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # CLAUDE.md
2
+
3
+ This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4
+
5
+ ## Project Overview
6
+
7
+ KaniTTS-2 is a Gradio-based web application for text-to-speech generation using the KaniTTS model family from PyPI. It's designed to run on HuggingFace Spaces with GPU acceleration via the `@spaces.GPU` decorator.
8
+
9
+ ## Running the Application
10
+
11
+ ```bash
12
+ python app.py
13
+ ```
14
+
15
+ The app launches on `0.0.0.0:7860` with a Gradio interface.
16
+
17
+ ## Architecture
18
+
19
+ ### Initialization Flow
20
+
21
+ The application follows a strict initialization sequence that must be maintained:
22
+
23
+ 1. **Dependency Setup** ([app.py:1-3](app.py#L1-L3)): `create_env.setup_dependencies()` runs first to install the dev version of transformers from GitHub. This uses a `/tmp/deps_installed` marker to prevent repeated installations.
24
+
25
+ 2. **Configuration Loading** ([app.py:12-13](app.py#L12-L13)): Loads `model_config.yaml` which defines multiple model checkpoints (e.g., "test-135000", "test-130000") with their HuggingFace paths and parameters.
26
+
27
+ 3. **Examples Loading** ([app.py:15-17](app.py#L15-L17)): Loads `examples.yaml` via the `Examples` adapter class, which transforms YAML examples into Gradio-compatible list-of-lists format.
28
+
29
+ 4. **Model Initialization** ([app.py:19-20](app.py#L19-L20)): `InitModels` loads all models upfront so the UI can switch between them without latency. Each model is a `KaniTTS` instance initialized directly with its config using unpacking (`**config`).
30
+
31
+ ### Key Components
32
+
33
+ **InitModels** ([util.py:24-57](util.py#L24-L57))
34
+ - Lazy initializer that constructs a map of `model_name -> KaniTTS`
35
+ - Loads all models immediately in `__call__` for zero-switching latency
36
+ - Each `KaniTTS` instance is initialized by unpacking its config directly: `KaniTTS(**config)`
37
+ - No longer requires `NemoAudioPlayer` or HuggingFace token as these are handled internally by the PyPI package
38
+
39
+ **Examples** ([util.py:59-97](util.py#L59-L97))
40
+ - Adapter converting YAML examples to Gradio `gr.Examples` rows
41
+ - Order must match UI inputs: `[text, model_dropdown, temp, top_p, rp]`
42
+ - Centralizes format and defaults so UI input order changes only require updates here and in [app.py](app.py)
43
+
44
+ **generate_speech_gpu** ([app.py:22-55](app.py#L22-L55))
45
+ - Decorated with `@spaces.GPU` for HuggingFace Spaces GPU allocation
46
+ - Calls the model directly: `audio, _ = model(text, temperature=t, top_p=top_p, repetition_penalty=rp)`
47
+ - Returns only the audio tuple: `(sample_rate, audio)` - the text output is ignored
48
+
49
+ ### Configuration Files
50
+
51
+ **model_config.yaml**
52
+ - Defines multiple model checkpoints under the `models` key
53
+ - Each model specifies: `model_name` (HF repo path), `device_map`, `use_bematts`, `audio_step`, `use_learnable_rope`
54
+ - All config parameters are passed directly to `KaniTTS` constructor via unpacking
55
+
56
+ **examples.yaml**
57
+ - List of example prompts with their generation parameters
58
+ - Each example requires: `text`, `model`, and optionally `temperature`, `top_p`, `repetition_penalty`
59
+ - Missing parameters fall back to defaults in [util.py:90-92](util.py#L90-L92): temperature=1.0, top_p=0.95, repetition_penalty=1.1
60
+
61
+ ### Dependencies
62
+
63
+ - **kani-tts==1.0.1**: Core TTS library from PyPI providing `KaniTTS` class with all inference logic
64
+ - **gradio>=4.0.0**: UI framework
65
+ - **transformers**: Installed from GitHub main branch via [create_env.py](create_env.py) for latest features
66
+
67
+ ### Environment Variables
68
+
69
+ - `OMP_NUM_THREADS=4`: Set in [create_env.py:6](create_env.py#L6) to limit OpenMP threading
70
+
71
+ ## Important Implementation Notes
72
+
73
+ ### Model Inference
74
+ - The KaniTTS model is called directly as a callable: `audio, text = model(text, temperature=..., top_p=..., repetition_penalty=...)`
75
+ - Returns tuple of `(audio, text)` but only audio is used in the UI
76
+ - No `max_tokens` parameter - the model handles sequence length internally
77
+ - Sample rate is hardcoded to 22050 Hz ([app.py:48](app.py#L48))
78
+
79
+ ### Example Caching
80
+ - Examples use `cache_examples=True` ([app.py:131](app.py#L131)) to pre-generate audio, speeding up demo interactions
81
+ - If you modify generation logic, cached examples may need regeneration
82
+
83
+ ### GPU Allocation
84
+ - The `@spaces.GPU` decorator is critical for HuggingFace Spaces deployment
85
+ - Without it, the app runs on CPU which is significantly slower
86
+ - Device selection falls back gracefully: `"cuda" if torch.cuda.is_available() else "cpu"` ([app.py:35](app.py#L35))
87
+
88
+ ### Input Order Dependency
89
+ - The order of inputs in `gr.Examples` ([app.py:128](app.py#L128)) must exactly match the order in `Examples.__call__` ([util.py:94](util.py#L94))
90
+ - Current order: `[text, model_dropdown, temp, top_p, rp]`
91
+ - Changing this requires updates in both locations
92
+
93
+ ### Removed Features
94
+ - **Speaker selection**: No longer supported in the new API
95
+ - **Time reporting**: Generation timing is not tracked
96
+ - **Max tokens slider**: Sequence length is handled automatically by the model
README.md CHANGED
@@ -1,15 +1,14 @@
1
  ---
2
- title: KaniTTS 2
3
- emoji: 👀
4
- colorFrom: blue
5
- colorTo: blue
6
  sdk: gradio
7
- sdk_version: 6.3.0
8
- python_version: '3.12'
9
  app_file: app.py
10
  pinned: false
11
  license: apache-2.0
12
- short_description: Pre Release space
13
  ---
14
 
15
  Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
1
  ---
2
+ title: KaniTTS-2
3
+ emoji: 😻
4
+ colorFrom: green
5
+ colorTo: gray
6
  sdk: gradio
7
+ sdk_version: 5.46.0
 
8
  app_file: app.py
9
  pinned: false
10
  license: apache-2.0
11
+
12
  ---
13
 
14
  Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
app.py ADDED
@@ -0,0 +1,124 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from create_env import setup_dependencies
2
+
3
+ setup_dependencies()
4
+
5
+ import spaces
6
+ import gradio as gr
7
+ from util import InitModels, load_config, Examples
8
+ import numpy as np
9
+ import torch
10
+
11
+ config = load_config("./model_config.yaml")
12
+ models_configs = config.models
13
+
14
+ examples_cfg = load_config("./examples.yaml")
15
+ examples_maker = Examples(examples_cfg)
16
+ examples = examples_maker()
17
+
18
+ init_models = InitModels(models_configs)
19
+ models = init_models()
20
+
21
+
22
+ @spaces.GPU
23
+ def generate_speech_gpu(text, model_choice, t, top_p, rp):
24
+ """
25
+ Generate speech from text using the selected model on GPU
26
+ """
27
+
28
+ if not text.strip():
29
+ return None
30
+
31
+ if not model_choice:
32
+ return None
33
+
34
+ try:
35
+ device = "cuda" if torch.cuda.is_available() else "cpu"
36
+ print(f"Using device: {device}")
37
+
38
+ selected_model = models[model_choice]
39
+
40
+ print(f"Generating speech with {model_choice}...")
41
+ audio, _ = selected_model(
42
+ text,
43
+ temperature=t,
44
+ top_p=top_p,
45
+ repetition_penalty=rp
46
+ )
47
+
48
+ sample_rate = 22050
49
+ print("Speech generation completed!")
50
+
51
+ return (sample_rate, audio)
52
+
53
+ except Exception as e:
54
+ print(f"Error during generation: {str(e)}")
55
+ return None
56
+
57
+ # Create Gradio interface
58
+ with gr.Blocks(title="😻 KaniTTS - Text to Speech", theme=gr.themes.Ocean()) as demo:
59
+ gr.Markdown("# 😻 KaniTTS: Fast and Expressive Speech Generation Model")
60
+ gr.Markdown("Select a model and enter text to generate emotional speech")
61
+
62
+ with gr.Row():
63
+ with gr.Column(scale=1):
64
+ model_dropdown = gr.Dropdown(
65
+ choices=list(models_configs.keys()),
66
+ value=list(models_configs.keys())[0],
67
+ label="Selected Model"
68
+ )
69
+
70
+ text_input = gr.Textbox(
71
+ label="Text",
72
+ placeholder="Enter your text ...",
73
+ lines=3,
74
+ max_lines=10
75
+ )
76
+
77
+ with gr.Accordion("Settings", open=False):
78
+ temp = gr.Slider(
79
+ minimum=0.1, maximum=1.5, value=0.6, step=0.05,
80
+ label="Temp",
81
+ )
82
+ top_p = gr.Slider(
83
+ minimum=0.1, maximum=1.0, value=0.95, step=0.05,
84
+ label="Top P",
85
+ )
86
+ rp = gr.Slider(
87
+ minimum=1.0, maximum=2.0, value=1.1, step=0.05,
88
+ label="Repetition Penalty",
89
+ )
90
+
91
+ generate_btn = gr.Button("Run", variant="primary", size="lg")
92
+
93
+
94
+ with gr.Column(scale=1):
95
+ audio_output = gr.Audio(
96
+ label="Generated Audio",
97
+ type="numpy"
98
+ )
99
+
100
+ # GPU generation event
101
+ generate_btn.click(
102
+ fn=generate_speech_gpu,
103
+ inputs=[text_input, model_dropdown, temp, top_p, rp],
104
+ outputs=[audio_output]
105
+ )
106
+
107
+ with gr.Row():
108
+
109
+ examples = examples
110
+
111
+ gr.Examples(
112
+ examples=examples,
113
+ inputs=[text_input, model_dropdown, temp, top_p, rp],
114
+ fn=generate_speech_gpu,
115
+ outputs=[audio_output],
116
+ cache_examples=True,
117
+ )
118
+
119
+ if __name__ == "__main__":
120
+ demo.launch(
121
+ server_name="0.0.0.0",
122
+ server_port=7860,
123
+ show_error=True
124
+ )
create_env.py ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import subprocess
3
+ import sys
4
+
5
+ def setup_dependencies():
6
+ os.environ["OMP_NUM_THREADS"] = "4"
7
+ try:
8
+ if os.path.exists('/tmp/deps_installed'):
9
+ return
10
+
11
+ print("Installing transformers dev version...")
12
+ subprocess.check_call([
13
+ sys.executable, "-m", "pip", "install", "--force-reinstall", "--no-cache-dir",
14
+ "git+https://github.com/huggingface/transformers.git"
15
+ ])
16
+
17
+ with open('/tmp/deps_installed', 'w') as f:
18
+ f.write('done')
19
+
20
+ except Exception as e:
21
+ print(f"Dependencies setup error: {e}")
examples.yaml ADDED
@@ -0,0 +1,45 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ examples:
2
+ - text: >-
3
+ No, that does not make you a failure. No, sweetie, no. It just, uh, it just means that you're having a tough time...
4
+ model: "test-135000"
5
+ temperature: 1
6
+ top_p: 0.95
7
+ repetition_penalty: 1.1
8
+
9
+ - text: >-
10
+ Anyway, um, so, um, tell me, tell me all about her. I mean, what's she like? Is she really, you know, pretty?
11
+ model: "test-135000"
12
+ temperature: 1
13
+ top_p: 0.95
14
+ repetition_penalty: 1.1
15
+
16
+ - text: >-
17
+ Have some wine, the March Hare said in an encouraging tone. Alice looked all round the table, but there was nothing on it but tea. I don't see any wine, she remarked. There isn't any, said the March Hare. Then it wasn't very civil of you to offer it, said Alice angrily. It wasn't very civil of you to sit down without being invited, said the March Hare. I didn't know it was YOUR table, said Alice; it's laid for a great many more than three. Your hair wants cutting, said the Hatter. He had been looking at Alice for some time with great curiosity, and this was his first speech. You should learn not to make personal remarks, Alice said with some severity; it's very rude.
18
+ model: "test-135000"
19
+ temperature: 1
20
+ top_p: 0.95
21
+ repetition_penalty: 1.1
22
+
23
+
24
+ - text: >-
25
+ Attention networks have proven to be an effective approach for embedding categorical inference within a deep neural network. However, for many tasks we may want to model richer structural dependencies without abandoning end-to-end training. In this work, we experiment with incorporating richer structural distributions, encoded using graphical models, within deep networks. We show that these structured attention networks are simple extensions of the basic attention procedure, and that they allow for extending attention beyond the standard softselection approach, such as attending to partial segmentations or to subtrees.
26
+ model: "test-135000"
27
+ temperature: 1
28
+ top_p: 0.95
29
+ repetition_penalty: 1.1
30
+
31
+ - text: >-
32
+ Кыргыз жери! Сенин ар бир ташыңда, ар бир тооңдо, ар бир сууңда менин жүрөгүмдүн бир бөлүгү бар. Сен менин ата-журтум, менин ыйык мекенимсиң.
33
+ model: "test-135000"
34
+ temperature: 1
35
+ top_p: 0.95
36
+ repetition_penalty: 1.1
37
+
38
+
39
+ - text: >-
40
+ ¿Será que todavía me recuerdas como antes?
41
+ model: "test-135000"
42
+ temperature: 1
43
+ top_p: 0.95
44
+ repetition_penalty: 1.1
45
+
model_config.yaml ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ models:
2
+
3
+ "test-135000":
4
+ model_name: nineninesix/full-checkpoint-135000
5
+ device_map: null
6
+ use_bematts: true
7
+ audio_step: 1.0
8
+ use_learnable_rope: true
9
+
10
+ "test-130000":
11
+ model_name: nineninesix/full-checkpoint-130000
12
+ device_map: null
13
+ use_bematts: true
14
+ audio_step: 1.0
15
+ use_learnable_rope: true
16
+
17
+ "test-110000":
18
+ model_name: nineninesix/full-checkpoint-110000
19
+ device_map: null
20
+ use_bematts: true
21
+ audio_step: 1.0
22
+ use_learnable_rope: true
23
+
24
+ "test-95000":
25
+ model_name: nineninesix/full-checkpoint-95000
26
+ device_map: null
27
+ use_bematts: true
28
+ audio_step: 1.0
29
+ use_learnable_rope: true
30
+
31
+ "test-25000":
32
+ model_name: nineninesix/full-checkpoint-25000
33
+ device_map: null
34
+ use_bematts: true
35
+ audio_step: 1.0
36
+ use_learnable_rope: true
37
+
38
+
requirements.txt ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ kani-tts==1.0.1
2
+ gradio>=4.0.0
util.py ADDED
@@ -0,0 +1,97 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from kani_tts import KaniTTS
2
+
3
+ import os
4
+ from omegaconf import OmegaConf
5
+
6
+
7
+ def load_config(config_path: str):
8
+ """Load configuration from a YAML file using OmegaConf.
9
+
10
+ Args:
11
+ config_path (str): Path to the YAML configuration file.
12
+
13
+ Returns:
14
+ Any: The loaded OmegaConf DictConfig.
15
+ """
16
+ resolved_path = os.path.abspath(config_path)
17
+ if not os.path.exists(resolved_path):
18
+ raise FileNotFoundError(f"Config file not found: {resolved_path}")
19
+ config = OmegaConf.load(resolved_path)
20
+ return config
21
+
22
+
23
+
24
+ class InitModels:
25
+
26
+ """
27
+ Lazy initializer that constructs a map of model name -> KaniTTS.
28
+
29
+ Parameters
30
+ ----------
31
+ models_configs : OmegaConf | DictConfig
32
+ The `models` section from `model_config.yaml` describing one or
33
+ more HF model checkpoints and their options (device_map, use_bematts, etc.).
34
+
35
+ Returns
36
+ -------
37
+ dict
38
+ When called, returns a dictionary `{model_name: KaniTTS}`.
39
+
40
+ Notes
41
+ -----
42
+ - All models are loaded immediately in `__call__` so the UI can list
43
+ them and switch between them without extra latency.
44
+ - Each KaniTTS instance is initialized with its config directly.
45
+ """
46
+
47
+ def __init__(self, models_configs: OmegaConf):
48
+ self.models_configs = models_configs
49
+
50
+ def __call__(self):
51
+ models = {}
52
+ for model_name, config in self.models_configs.items():
53
+ print(f"Loading {model_name}...")
54
+ models[model_name] = KaniTTS(**config)
55
+ print(f"{model_name} loaded!")
56
+ print("All models loaded!")
57
+ return models
58
+
59
+ class Examples:
60
+
61
+ """
62
+ Adapter that converts YAML examples into Gradio `gr.Examples` rows.
63
+
64
+ Parameters
65
+ ----------
66
+ exam_cfg : OmegaConf | DictConfig
67
+ Parsed contents of `examples.yaml`. Expected structure:
68
+ `examples: [ {text, model, temperature?, top_p?, repetition_penalty?}, ... ]`.
69
+
70
+ Behavior
71
+ --------
72
+ - Produces a list-of-lists whose order must match the `inputs` order
73
+ used when constructing `gr.Examples` in `app.py`.
74
+ - Current order: `[text, model_dropdown, temp, top_p, rp]`.
75
+
76
+ Why this exists
77
+ ---------------
78
+ - Keeps format and defaults centralized, so changing the UI inputs
79
+ order only requires a single change here and in `app.py`.
80
+ """
81
+
82
+ def __init__(self, exam_cfg: OmegaConf):
83
+ self.exam_cfg = exam_cfg
84
+
85
+ def __call__(self) -> list[list]:
86
+ rows = []
87
+ for e in self.exam_cfg.examples:
88
+ text = e.get("text")
89
+ model = e.get("model")
90
+ temperature = e.get("temperature", 1.0)
91
+ top_p = e.get("top_p", 0.95)
92
+ repetition_penalty = e.get("repetition_penalty", 1.1)
93
+ # Order must match gr.Examples inputs: [text, model_dropdown, temp, top_p, rp]
94
+ rows.append([text, model, temperature, top_p, repetition_penalty])
95
+
96
+ return rows
97
+