windextts 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
windextts/__init__.py ADDED
@@ -0,0 +1,3 @@
1
+ """WIndexTTS: pure-torch accelerated inference for IndexTTS-2.5."""
2
+
3
+ __version__ = "0.1.0"
windextts/__main__.py ADDED
@@ -0,0 +1,5 @@
1
+ """python -m windextts → windextts.cli:main"""
2
+ from windextts.cli import main
3
+
4
+ if __name__ == "__main__":
5
+ raise SystemExit(main())
windextts/attention.py ADDED
@@ -0,0 +1,154 @@
1
+ """Adaptive SDPA (Scaled Dot-Product Attention) backend selection.
2
+
3
+ Design goal: **zero external attention dependency** in the core inference path.
4
+ We use only ``torch.nn.functional.scaled_dot_product_attention`` (SDPA) with
5
+ explicit backend pinning via ``torch.nn.attention.sdpa_kernel``. No ``flash_attn``
6
+ import, no triton — works on Windows with stock torch wheels.
7
+
8
+ Backend policy (verified on torch 2.8.0+cu128, A10G):
9
+ - prefill (seq > 1, e.g. GPT conditioning / DiT): ``CUDNN_ATTENTION``.
10
+ Ships inside the torch CUDA wheel (cross-platform, no extra install).
11
+ Measured ~= flash_attn speed, slightly faster.
12
+ - decode (q seq == 1, GPT autoregressive step): cuDNN is *rejected* by
13
+ PyTorch's sdp_utils guard ("cudnn SDPA does not support sequence length 1"
14
+ — conservative perf guard in sdp_utils.cpp:458, not a library limitation).
15
+ Use ``FLASH_ATTENTION`` on Linux, ``EFFICIENT_ATTENTION`` on Windows
16
+ (mem_eff is the Windows-safe fallback; official flash backend needs a
17
+ wheel match).
18
+
19
+ The kernel choice is made per-call (cheap) so a single module can mix prefill
20
+ and decode without two code paths. All public helpers accept the standard SDPA
21
+ tensors and forward ``is_causal`` / ``scale``.
22
+ """
23
+ from __future__ import annotations
24
+
25
+ import os
26
+
27
+ import torch
28
+ import torch.nn.functional as F
29
+ from torch.nn.attention import SDPBackend, sdpa_kernel
30
+
31
+ # ---------------------------------------------------------------------------
32
+ # Platform detection — Windows gets mem_eff for decode; Linux gets flash.
33
+ # cuDNN is always preferred for prefill where available.
34
+ # ---------------------------------------------------------------------------
35
+
36
+ _IS_WINDOWS = os.name == "nt" or os.environ.get("WINDEXTTS_FORCE_WIN") == "1"
37
+ # Allow override for benchmarking / debugging.
38
+ _FORCE_PREFILL = os.environ.get("WINDEXTTS_ATTN_PREFILL", "") # cudnn|flash|eff|math
39
+ _FORCE_DECODE = os.environ.get("WINDEXTTS_ATTN_DECODE", "")
40
+
41
+
42
+ def _backend_for_prefill() -> SDPBackend:
43
+ if _FORCE_PREFILL == "flash":
44
+ return SDPBackend.FLASH_ATTENTION
45
+ if _FORCE_PREFILL == "eff":
46
+ return SDPBackend.EFFICIENT_ATTENTION
47
+ if _FORCE_PREFILL == "math":
48
+ return SDPBackend.MATH
49
+ # Default: cuDNN (fastest, bundled in torch CUDA wheel).
50
+ return SDPBackend.CUDNN_ATTENTION
51
+
52
+
53
+ def _backend_for_decode() -> SDPBackend:
54
+ if _FORCE_DECODE == "flash":
55
+ return SDPBackend.FLASH_ATTENTION
56
+ if _FORCE_DECODE == "eff":
57
+ return SDPBackend.EFFICIENT_ATTENTION
58
+ if _FORCE_DECODE == "math":
59
+ return SDPBackend.MATH
60
+ # cuDNN cannot run with query seq==1 (sdp_utils.cpp:458 guard). Pick the
61
+ # best available: flash on Linux, mem_eff on Windows (no wheel dependency).
62
+ return SDPBackend.EFFICIENT_ATTENTION if _IS_WINDOWS else SDPBackend.FLASH_ATTENTION
63
+
64
+
65
+ def _is_decode(query: torch.Tensor) -> bool:
66
+ """A decode step has query sequence length == 1 (single new token)."""
67
+ return query.size(-2) == 1
68
+
69
+
70
+ # ---------------------------------------------------------------------------
71
+ # Public API
72
+ # ---------------------------------------------------------------------------
73
+
74
+
75
+ def attn(
76
+ query: torch.Tensor,
77
+ key: torch.Tensor,
78
+ value: torch.Tensor,
79
+ *,
80
+ attn_mask: torch.Tensor | None = None,
81
+ dropout_p: float = 0.0,
82
+ is_causal: bool = False,
83
+ scale: float | None = None,
84
+ ) -> torch.Tensor:
85
+ """Adaptive-backend scaled dot-product attention.
86
+
87
+ Picks cuDNN for prefill (seq>1) and flash/mem_eff for decode (q seq==1),
88
+ matching the measured-optimal backend per shape.
89
+
90
+ Args:
91
+ query/key/value: [..., seq, head_dim] tensors (leading dims broadcast).
92
+ attn_mask, dropout_p, is_causal, scale: forwarded to SDPA.
93
+
94
+ Returns:
95
+ Attention output, same shape as ``query``.
96
+ """
97
+ backend = _backend_for_decode() if _is_decode(query) else _backend_for_prefill()
98
+ with sdpa_kernel([backend]):
99
+ return F.scaled_dot_product_attention(
100
+ query,
101
+ key,
102
+ value,
103
+ attn_mask=attn_mask,
104
+ dropout_p=dropout_p,
105
+ is_causal=is_causal,
106
+ scale=scale,
107
+ )
108
+
109
+
110
+ def attn_prefill(
111
+ query: torch.Tensor,
112
+ key: torch.Tensor,
113
+ value: torch.Tensor,
114
+ **kwargs,
115
+ ) -> torch.Tensor:
116
+ """Prefill-path attention — pinned to cuDNN (seq>1, e.g. DiT / GPT cond)."""
117
+ with sdpa_kernel([_backend_for_prefill()]):
118
+ return F.scaled_dot_product_attention(query, key, value, **kwargs)
119
+
120
+
121
+ def attn_decode(
122
+ query: torch.Tensor,
123
+ key: torch.Tensor,
124
+ value: torch.Tensor,
125
+ **kwargs,
126
+ ) -> torch.Tensor:
127
+ """Decode-path attention — flash(Linux)/mem_eff(Win), for q seq==1."""
128
+ with sdpa_kernel([_backend_for_decode()]):
129
+ return F.scaled_dot_product_attention(query, key, value, **kwargs)
130
+
131
+
132
+ if __name__ == "__main__":
133
+ # Smoke test: verify backend selection and that decode falls back correctly.
134
+ dev = "cuda" if torch.cuda.is_available() else "cpu"
135
+ dt = torch.bfloat16 if dev == "cuda" else torch.float32
136
+ print(f"platform: {'windows' if _IS_WINDOWS else 'linux'}, torch {torch.__version__}")
137
+ print(
138
+ f"prefill backend: {_backend_for_prefill().name} | "
139
+ f"decode backend: {_backend_for_decode().name}"
140
+ )
141
+
142
+ # prefill
143
+ q = k = v = torch.randn(1, 16, 512, 64, device=dev, dtype=dt)
144
+ o1 = attn(q, k, v, is_causal=True)
145
+ assert o1.shape == q.shape
146
+ print(f"prefill ok, out {tuple(o1.shape)}")
147
+
148
+ # decode (q seq=1)
149
+ qd = torch.randn(1, 16, 1, 64, device=dev, dtype=dt)
150
+ kd = vd = torch.randn(1, 16, 512, 64, device=dev, dtype=dt)
151
+ o2 = attn(qd, kd, vd)
152
+ assert o2.shape == qd.shape
153
+ print(f"decode ok, out {tuple(o2.shape)}")
154
+ print("SMOKE OK")
windextts/cli.py ADDED
@@ -0,0 +1,188 @@
1
+ """WIndexTTS CLI — synthesize speech from the command line.
2
+
3
+ Usage:
4
+ # install model weights first (~7.5GB, one time, resumable)
5
+ windextts --install-model --model-dir /path/to/IndexTTS-2.5
6
+
7
+ # basic (fp16, GPU)
8
+ windextts --ref ref.wav --text "你好世界" -o out.wav
9
+
10
+ # W4A16 quantized GPT (fastest)
11
+ windextts --ref ref.wav --text "你好世界" -o out.wav --w4a16
12
+
13
+ # low VRAM (3-4GB GPUs; keeps beam3, ~2.9GB steady)
14
+ windextts --ref ref.wav --text "你好世界" -o out.wav --w4a16 --low-vram
15
+
16
+ # from a text file, with emotion vector + duration control
17
+ windextts --ref ref.wav --text-file story.txt -o out.wav \
18
+ --emo-vector 0.8,0,0,0,0,0,0.2,0 --duration 1.1
19
+
20
+ # fp32 reference precision, verbose timing
21
+ windextts --ref ref.wav --text "hello" -o out.wav --fp32 --verbose
22
+ """
23
+
24
+ from __future__ import annotations
25
+
26
+ import argparse
27
+ import os
28
+ import sys
29
+ import time
30
+
31
+
32
+ def build_parser() -> argparse.ArgumentParser:
33
+ p = argparse.ArgumentParser(
34
+ prog="windextts",
35
+ description="WIndexTTS: pure-torch accelerated IndexTTS-2.5 inference "
36
+ "(zero JIT compile, Windows friendly).",
37
+ formatter_class=argparse.RawDescriptionHelpFormatter,
38
+ epilog=__doc__.split("Usage:")[-1] if __doc__ else None,
39
+ )
40
+ # required inputs
41
+ p.add_argument("--ref", required=True, help="reference audio path (5-15s clean speech)")
42
+ p.add_argument("--text", help="text to synthesize (or use --text-file)")
43
+ p.add_argument("--text-file", help="read text from a file (UTF-8)")
44
+ p.add_argument("-o", "--output", default="output.wav", help="output wav path")
45
+
46
+ # engine mode
47
+ mode = p.add_mutually_exclusive_group()
48
+ mode.add_argument("--fp32", action="store_true", help="fp32 weights (highest precision, ~7.6GB)")
49
+ mode.add_argument("--w4a16", action="store_true",
50
+ help="W4A16 INT4 GPT quantization (fastest; needs torchao)")
51
+ p.add_argument("--low-vram", action="store_true",
52
+ help="low-VRAM mode for 3-4GB GPUs (w2v streamed to CPU, "
53
+ "beam3 kept, ~2.9GB steady)")
54
+ p.add_argument("--model-dir", default=None, help="weights directory (gpt.pth, config.yaml, ...)")
55
+
56
+ # language / prosody
57
+ p.add_argument("--lang", default="ZH",
58
+ help="language token (ZH/EN/JA/KO/YUE/..., default ZH)")
59
+ p.add_argument("--duration", type=float, default=1.0,
60
+ help="duration factor (official 1.72 scale × factor, default 1.0)")
61
+
62
+ # emotion (mutually exclusive sources)
63
+ emo = p.add_mutually_exclusive_group()
64
+ emo.add_argument("--emo-vector",
65
+ help="8-dim emotion weights happy,angry,sad,afraid,disgusted,"
66
+ "melancholic,surprised,calm — e.g. 0.8,0,0,0,0,0,0.2,0")
67
+ emo.add_argument("--emo-text", help="free-text emotion description (QwenEmotion)")
68
+ emo.add_argument("--emo-ref", help="emotion reference audio path (conformer path)")
69
+
70
+ # sampling
71
+ p.add_argument("--greedy", action="store_true", help="greedy decode (beam=1, deterministic)")
72
+ p.add_argument("--top-p", type=float, default=0.8)
73
+ p.add_argument("--top-k", type=int, default=30)
74
+ p.add_argument("--temperature", type=float, default=0.8)
75
+
76
+ # perf knobs
77
+ p.add_argument("--cfm-steps", type=int, default=12,
78
+ help="S2Mel CFM Euler steps (official 25, default 12)")
79
+ p.add_argument("--teacache", type=float, default=0.25,
80
+ help="TeaCache threshold (0=off, default 0.25)")
81
+ p.add_argument("--no-normalize", action="store_true",
82
+ help="skip text normalization (G2P digits/punctuation)")
83
+ p.add_argument("--segment-tokens", type=int, default=120,
84
+ help="max text tokens per segment (long texts auto-split)")
85
+
86
+ p.add_argument("--verbose", action="store_true", help="print per-run timing + VRAM")
87
+
88
+ # model install (download weights without synthesizing) — ModelScope direct
89
+ # links, no SDK dependency, resumable
90
+ p.add_argument("--install-model", action="store_true",
91
+ help="download model weights to --model-dir (or WINDEXTTS_WEIGHTS_DIR) "
92
+ "and exit; ~7.5GB from ModelScope direct links. Resumable. "
93
+ "Combine with --skip-qwen.")
94
+ p.add_argument("--skip-qwen", action="store_true",
95
+ help="with --install-model: skip qwen0.6bemo4-merge (1.2GB, only "
96
+ "needed for emo_text)")
97
+ return p
98
+
99
+
100
+ def main(argv: list[str] | None = None) -> int:
101
+ args = build_parser().parse_args(argv)
102
+
103
+ # --- model install mode ---
104
+ if args.install_model:
105
+ from windextts.download import download_model
106
+ target = args.model_dir or os.environ.get("WINDEXTTS_WEIGHTS_DIR")
107
+ if not target:
108
+ print("error: --install-model needs --model-dir or WINDEXTTS_WEIGHTS_DIR",
109
+ file=sys.stderr)
110
+ return 2
111
+ download_model(target, include_qwen=not args.skip_qwen)
112
+ return 0
113
+
114
+ # --- text source ---
115
+ if args.text_file:
116
+ with open(args.text_file, encoding="utf-8") as f:
117
+ text = f.read()
118
+ elif args.text:
119
+ text = args.text
120
+ else:
121
+ print("error: either --text or --text-file is required", file=sys.stderr)
122
+ return 2
123
+ text = text.strip()
124
+ if not text:
125
+ print("error: empty text", file=sys.stderr)
126
+ return 2
127
+
128
+ # --- emotion vector parsing ---
129
+ emo_vector = None
130
+ if args.emo_vector:
131
+ try:
132
+ emo_vector = [float(x) for x in args.emo_vector.split(",")]
133
+ except ValueError:
134
+ print("error: --emo-vector must be 8 comma-separated floats", file=sys.stderr)
135
+ return 2
136
+ if len(emo_vector) != 8:
137
+ print("error: --emo-vector needs exactly 8 values "
138
+ "(happy,angry,sad,afraid,disgusted,melancholic,surprised,calm)", file=sys.stderr)
139
+ return 2
140
+
141
+ # --- engine ---
142
+ import torch
143
+ from windextts.inference import WIndexTTS
144
+
145
+ dtype = torch.float32 if args.fp32 else torch.float16
146
+ t0 = time.perf_counter()
147
+ tts = WIndexTTS(
148
+ weights_dir=args.model_dir, device="cuda", dtype=dtype,
149
+ enable_w4a16=args.w4a16, low_vram=args.low_vram,
150
+ )
151
+ tts.warmup()
152
+ if args.verbose:
153
+ print(f"[load+warmup] {time.perf_counter() - t0:.1f}s "
154
+ f"VRAM alloc {torch.cuda.memory_allocated() / 1e9:.2f}GB", file=sys.stderr)
155
+
156
+ # --- synthesize ---
157
+ t0 = time.perf_counter()
158
+ sr, audio = tts.infer(
159
+ spk_audio_prompt=args.ref,
160
+ text=text,
161
+ lang=args.lang,
162
+ emo_vector=emo_vector,
163
+ emo_text=args.emo_text,
164
+ emo_ref_path=args.emo_ref,
165
+ duration_factor=args.duration,
166
+ do_sample=not args.greedy,
167
+ top_p=args.top_p,
168
+ top_k=args.top_k,
169
+ temperature=args.temperature,
170
+ cfm_steps=args.cfm_steps,
171
+ teacache_thresh=args.teacache,
172
+ text_normalization=not args.no_normalize,
173
+ max_text_tokens_per_segment=args.segment_tokens,
174
+ num_beams=1 if args.greedy else 3,
175
+ )
176
+ dt = time.perf_counter() - t0
177
+ dur = audio.numel() / sr
178
+
179
+ import soundfile as sf
180
+ sf.write(args.output, audio.float().cpu().numpy().squeeze(), sr)
181
+ print(f"{args.output} ({dur:.2f}s audio in {dt * 1000:.0f}ms, RTF={dt / dur:.3f}, {sr}Hz)")
182
+ if args.verbose:
183
+ print(f"[peak VRAM] {torch.cuda.max_memory_allocated() / 1e9:.2f}GB", file=sys.stderr)
184
+ return 0
185
+
186
+
187
+ if __name__ == "__main__":
188
+ sys.exit(main())
windextts/config.py ADDED
@@ -0,0 +1,326 @@
1
+ """Typed access to the IndexTTS-2.5 config.yaml.
2
+
3
+ This is the single source of truth for hyperparameters. The ``Config`` object
4
+ is constructed once from ``/root/IndexTTS-2.5/config.yaml`` (or a copied file)
5
+ and passed to every module. No module should hardcode magic numbers that exist
6
+ in config.yaml — read them here.
7
+
8
+ Magic numbers that live in source code (infer_v2_5.py), not config.yaml — e.g.
9
+ ``w2v-bert hidden_states[17]``, ``1.72`` duration scale, ``diffusion_steps=25`` —
10
+ are collected in :class:`RuntimeConstants` so they are also centralized.
11
+ """
12
+ from __future__ import annotations
13
+
14
+ import os
15
+
16
+ from dataclasses import dataclass, field
17
+ from pathlib import Path
18
+
19
+ import yaml
20
+
21
+ # ---------------------------------------------------------------------------
22
+ # Runtime constants — these come from infer_v2_5.py source, NOT config.yaml.
23
+ # Every value must cite its source line. Do NOT invent or tweak.
24
+ # ---------------------------------------------------------------------------
25
+
26
+
27
+ @dataclass(frozen=True)
28
+ class RuntimeConstants:
29
+ """Constants hardcoded in indextts/infer_v2_5.py (not in config.yaml).
30
+
31
+ Each field cites the source line in ``infer_v2_5.py``. These are the
32
+ "seam magic numbers" — changing any of them produces wrong output.
33
+ """
34
+
35
+ # w2v-bert: which hidden_states layer to use as ref audio feature.
36
+ # infer_v2_5.py:288 hidden_states[17]
37
+ w2v_layer: int = 17
38
+
39
+ # w2v-bert feature normalization: (feat - mean) / sqrt(var)
40
+ # stats loaded from wav2vec2bert_stats.pt {mean:[1024], var:[1024]}
41
+ # infer_v2_5.py:177-179, 289
42
+ # (handled in weights/frontend, kept here only as documentation anchor)
43
+
44
+ # S2Mel duration scaling. infer_v2_5.py:855 1.72 * duration_factor
45
+ s2mel_duration_scale: float = 1.72
46
+
47
+ # S2Mel length_regulator n_quantizers. infer_v2_5.py:655,859
48
+ s2mel_n_quantizers: int = 3
49
+
50
+ # S2Mel CFM. infer_v2_5.py:849-850 diffusion_steps=25, inference_cfg_rate=0.7
51
+ cfm_diffusion_steps: int = 25
52
+ cfm_inference_cfg_rate: float = 0.7
53
+
54
+ # Output audio. infer_v2_5.py:514 22050 Hz mono
55
+ output_sr: int = 22050
56
+
57
+ # Reference audio resample targets. infer_v2_5.py:628-629
58
+ ref_sr_mel: int = 22000 # for mel / mel-condition path
59
+ ref_sr_w2v: int = 16000 # for w2v-bert & campplus
60
+
61
+ # Max reference audio length taken (seconds). infer_v2_5.py path
62
+ ref_max_seconds: float = 15.0
63
+
64
+
65
+ # ---------------------------------------------------------------------------
66
+ # config.yaml sections — mirrors /root/IndexTTS-2.5/config.yaml structure.
67
+ # Kept loosely typed (nested dataclasses of plain values) to avoid coupling to
68
+ # any external library's config schema.
69
+ # ---------------------------------------------------------------------------
70
+
71
+
72
+ @dataclass
73
+ class MelConfig:
74
+ sample_rate: int = 24000
75
+ n_fft: int = 1024
76
+ hop_length: int = 256
77
+ win_length: int = 1024
78
+ n_mels: int = 100
79
+ mel_fmin: float = 0.0
80
+ normalize: bool = False
81
+
82
+
83
+ @dataclass
84
+ class GPTConfig:
85
+ model_dim: int = 1280
86
+ max_mel_tokens: int = 1815
87
+ max_text_tokens: int = 600
88
+ heads: int = 20
89
+ use_mel_codes_as_input: bool = True
90
+ mel_length_compression: int = 1024
91
+ layers: int = 24
92
+ number_text_tokens: int = 60509
93
+ number_mel_codes: int = 8194
94
+ start_mel_token: int = 8192
95
+ stop_mel_token: int = 8193
96
+ start_text_token: int = 0
97
+ stop_text_token: int = 1
98
+ train_solo_embeddings: bool = False
99
+ condition_type: str = "conformer_perceiver"
100
+ # condition_module / emo_condition_module are nested dicts; kept as-is.
101
+ condition_module: dict = field(default_factory=dict)
102
+ emo_condition_module: dict = field(default_factory=dict)
103
+
104
+
105
+ @dataclass
106
+ class SemanticCodecConfig:
107
+ codebook_size: int = 8192
108
+ hidden_size: int = 1024
109
+ codebook_dim: int = 8
110
+ vocos_dim: int = 384
111
+ vocos_intermediate_dim: int = 2048
112
+ vocos_num_layers: int = 12
113
+
114
+
115
+ @dataclass
116
+ class S2MelDiTConfig:
117
+ hidden_dim: int = 512
118
+ num_heads: int = 8
119
+ depth: int = 13
120
+ class_dropout_prob: float = 0.1
121
+ block_size: int = 8192
122
+ in_channels: int = 80
123
+ style_condition: bool = True
124
+ final_layer_type: str = "wavenet"
125
+ target: str = "mel"
126
+ content_dim: int = 512
127
+ content_codebook_size: int = 1024
128
+ content_type: str = "discrete"
129
+ f0_condition: bool = False
130
+ n_f0_bins: int = 512
131
+ content_codebooks: int = 1
132
+ is_causal: bool = False
133
+ long_skip_connection: bool = True
134
+ zero_prompt_speech_token: bool = False
135
+ time_as_token: bool = False
136
+ style_as_token: bool = False
137
+ uvit_skip_connection: bool = True
138
+ add_resblock_in_transformer: bool = False
139
+
140
+
141
+ @dataclass
142
+ class S2MelLengthRegConfig:
143
+ channels: int = 512
144
+ is_discrete: bool = False
145
+ in_channels: int = 1024
146
+ content_codebook_size: int = 2048
147
+ sampling_ratios: tuple = (1, 1, 1, 1)
148
+ vector_quantize: bool = False
149
+ n_codebooks: int = 1
150
+ quantizer_dropout: float = 0.0
151
+ f0_condition: bool = False
152
+ n_f0_bins: int = 512
153
+
154
+
155
+ @dataclass
156
+ class S2MelConfig:
157
+ # preprocess_params.spect_params
158
+ sr: int = 22050
159
+ n_fft: int = 1024
160
+ win_length: int = 1024
161
+ hop_length: int = 256
162
+ n_mels: int = 80
163
+ fmin: float = 0.0
164
+ fmax: float | None = None
165
+ dit_type: str = "DiT"
166
+ reg_loss_type: str = "l1"
167
+ style_encoder_dim: int = 192
168
+ length_reg: S2MelLengthRegConfig = field(default_factory=S2MelLengthRegConfig)
169
+ dit: S2MelDiTConfig = field(default_factory=S2MelDiTConfig)
170
+ wavenet_hidden_dim: int = 512
171
+ wavenet_num_layers: int = 8
172
+ wavenet_kernel_size: int = 5
173
+ wavenet_dilation_rate: int = 1
174
+ wavenet_p_dropout: float = 0.2
175
+ wavenet_style_condition: bool = True
176
+
177
+
178
+ @dataclass
179
+ class Config:
180
+ """Top-level config mirroring IndexTTS-2.5 config.yaml."""
181
+
182
+ # dataset.mel
183
+ mel: MelConfig = field(default_factory=MelConfig)
184
+ gpt: GPTConfig = field(default_factory=GPTConfig)
185
+ semantic_codec: SemanticCodecConfig = field(default_factory=SemanticCodecConfig)
186
+ s2mel: S2MelConfig = field(default_factory=S2MelConfig)
187
+ # vocoder
188
+ vocoder_type: str = "bigvgan"
189
+ vocoder_name: str = "bigvgan_generator.pt"
190
+ version: str = "2.5"
191
+ # raw dict fallback for anything not yet dataclassed
192
+ _raw: dict = field(default_factory=dict, repr=False)
193
+ # runtime constants from infer_v2_5.py
194
+ rt: RuntimeConstants = field(default_factory=RuntimeConstants)
195
+
196
+ @classmethod
197
+ def from_yaml(cls, path: str | Path) -> "Config":
198
+ with open(path, "r", encoding="utf-8") as f:
199
+ raw = yaml.safe_load(f)
200
+
201
+ ds = raw.get("dataset", {}) or {}
202
+ mel_raw = ds.get("mel", {}) or {}
203
+ mel = MelConfig(
204
+ sample_rate=mel_raw.get("sample_rate", 24000),
205
+ n_fft=mel_raw.get("n_fft", 1024),
206
+ hop_length=mel_raw.get("hop_length", 256),
207
+ win_length=mel_raw.get("win_length", 1024),
208
+ n_mels=mel_raw.get("n_mels", 100),
209
+ mel_fmin=mel_raw.get("mel_fmin", 0.0),
210
+ normalize=mel_raw.get("normalize", False),
211
+ )
212
+
213
+ g = raw.get("gpt", {}) or {}
214
+ gpt = GPTConfig(
215
+ model_dim=g.get("model_dim", 1280),
216
+ max_mel_tokens=g.get("max_mel_tokens", 1815),
217
+ max_text_tokens=g.get("max_text_tokens", 600),
218
+ heads=g.get("heads", 20),
219
+ use_mel_codes_as_input=g.get("use_mel_codes_as_input", True),
220
+ mel_length_compression=g.get("mel_length_compression", 1024),
221
+ layers=g.get("layers", 24),
222
+ number_text_tokens=g.get("number_text_tokens", 60509),
223
+ number_mel_codes=g.get("number_mel_codes", 8194),
224
+ start_mel_token=g.get("start_mel_token", 8192),
225
+ stop_mel_token=g.get("stop_mel_token", 8193),
226
+ start_text_token=g.get("start_text_token", 0),
227
+ stop_text_token=g.get("stop_text_token", 1),
228
+ train_solo_embeddings=g.get("train_solo_embeddings", False),
229
+ condition_type=g.get("condition_type", "conformer_perceiver"),
230
+ condition_module=g.get("condition_module", {}) or {},
231
+ emo_condition_module=g.get("emo_condition_module", {}) or {},
232
+ )
233
+
234
+ sc = raw.get("semantic_codec", {}) or {}
235
+ semantic_codec = SemanticCodecConfig(
236
+ codebook_size=sc.get("codebook_size", 8192),
237
+ hidden_size=sc.get("hidden_size", 1024),
238
+ codebook_dim=sc.get("codebook_dim", 8),
239
+ vocos_dim=sc.get("vocos_dim", 384),
240
+ vocos_intermediate_dim=sc.get("vocos_intermediate_dim", 2048),
241
+ vocos_num_layers=sc.get("vocos_num_layers", 12),
242
+ )
243
+
244
+ s2 = raw.get("s2mel", {}) or {}
245
+ pre = (s2.get("preprocess_params", {}) or {}).get("spect_params", {}) or {}
246
+ fmax_raw = pre.get("fmax", None)
247
+ fmax = None if (fmax_raw is None or fmax_raw == "None") else float(fmax_raw)
248
+ lr_raw = s2.get("length_regulator", {}) or {}
249
+ length_reg = S2MelLengthRegConfig(
250
+ channels=lr_raw.get("channels", 512),
251
+ is_discrete=lr_raw.get("is_discrete", False),
252
+ in_channels=lr_raw.get("in_channels", 1024),
253
+ content_codebook_size=lr_raw.get("content_codebook_size", 2048),
254
+ sampling_ratios=tuple(lr_raw.get("sampling_ratios", [1, 1, 1, 1])),
255
+ vector_quantize=lr_raw.get("vector_quantize", False),
256
+ n_codebooks=lr_raw.get("n_codebooks", 1),
257
+ quantizer_dropout=lr_raw.get("quantizer_dropout", 0.0),
258
+ f0_condition=lr_raw.get("f0_condition", False),
259
+ n_f0_bins=lr_raw.get("n_f0_bins", 512),
260
+ )
261
+ dit_raw = s2.get("DiT", {}) or {}
262
+ dit = S2MelDiTConfig(
263
+ hidden_dim=dit_raw.get("hidden_dim", 512),
264
+ num_heads=dit_raw.get("num_heads", 8),
265
+ depth=dit_raw.get("depth", 13),
266
+ class_dropout_prob=dit_raw.get("class_dropout_prob", 0.1),
267
+ block_size=dit_raw.get("block_size", 8192),
268
+ in_channels=dit_raw.get("in_channels", 80),
269
+ style_condition=dit_raw.get("style_condition", True),
270
+ final_layer_type=dit_raw.get("final_layer_type", "wavenet"),
271
+ target=dit_raw.get("target", "mel"),
272
+ content_dim=dit_raw.get("content_dim", 512),
273
+ content_codebook_size=dit_raw.get("content_codebook_size", 1024),
274
+ content_type=dit_raw.get("content_type", "discrete"),
275
+ f0_condition=dit_raw.get("f0_condition", False),
276
+ n_f0_bins=dit_raw.get("n_f0_bins", 512),
277
+ content_codebooks=dit_raw.get("content_codebooks", 1),
278
+ is_causal=dit_raw.get("is_causal", False),
279
+ long_skip_connection=dit_raw.get("long_skip_connection", True),
280
+ zero_prompt_speech_token=dit_raw.get("zero_prompt_speech_token", False),
281
+ time_as_token=dit_raw.get("time_as_token", False),
282
+ style_as_token=dit_raw.get("style_as_token", False),
283
+ uvit_skip_connection=dit_raw.get("uvit_skip_connection", True),
284
+ add_resblock_in_transformer=dit_raw.get("add_resblock_in_transformer", False),
285
+ )
286
+ wn = s2.get("wavenet", {}) or {}
287
+ s2mel = S2MelConfig(
288
+ sr=pre.get("sr", 22050),
289
+ n_fft=pre.get("n_fft", 1024),
290
+ win_length=pre.get("win_length", 1024),
291
+ hop_length=pre.get("hop_length", 256),
292
+ n_mels=pre.get("n_mels", 80),
293
+ fmin=pre.get("fmin", 0.0),
294
+ fmax=fmax,
295
+ dit_type=s2.get("dit_type", "DiT"),
296
+ reg_loss_type=s2.get("reg_loss_type", "l1"),
297
+ style_encoder_dim=(s2.get("style_encoder", {}) or {}).get("dim", 192),
298
+ length_reg=length_reg,
299
+ dit=dit,
300
+ wavenet_hidden_dim=wn.get("hidden_dim", 512),
301
+ wavenet_num_layers=wn.get("num_layers", 8),
302
+ wavenet_kernel_size=wn.get("kernel_size", 5),
303
+ wavenet_dilation_rate=wn.get("dilation_rate", 1),
304
+ wavenet_p_dropout=wn.get("p_dropout", 0.2),
305
+ wavenet_style_condition=wn.get("style_condition", True),
306
+ )
307
+
308
+ voc = raw.get("vocoder", {}) or {}
309
+ return cls(
310
+ mel=mel,
311
+ gpt=gpt,
312
+ semantic_codec=semantic_codec,
313
+ s2mel=s2mel,
314
+ vocoder_type=voc.get("type", "bigvgan"),
315
+ vocoder_name=voc.get("name", "bigvgan_generator.pt"),
316
+ version=raw.get("version", "2.5"),
317
+ _raw=raw,
318
+ )
319
+
320
+
321
+ DEFAULT_CONFIG_PATH = os.environ.get("WINDEXTTS_WEIGHTS_DIR", "/root/IndexTTS-2.5") + "/config.yaml"
322
+
323
+
324
+ def load_default_config() -> Config:
325
+ """Load config from the canonical IndexTTS-2.5 weights dir."""
326
+ return Config.from_yaml(DEFAULT_CONFIG_PATH)
Binary file
Binary file