The 'modelscope' PyPI package ships no console entry points; the
'modelscope' CLI is provided by its dependency 'modelscope-hub'
(confirmed via the wheel's entry_points metadata, modelscope 1.40.1 /
modelscope-hub 0.4.3). Plain 'pip install modelscope' works because the
dependency lands in the same environment, but 'uv tool install
"modelscope"' creates an isolated tool environment and fails with
'No executables are provided by package modelscope'.
Verified locally: 'uv tool install modelscope-hub' installs cleanly and
'modelscope download ...' works.
Co-authored-by: ardss <ardss@users.noreply.github.com>
The speaker-reference cache-miss path calls get_emb twice with identical
inputs; the second result (S_ref) is never used (its only remaining
reference is a commented-out argument). The duplicate call runs a full
W2V-BERT forward on every cache miss (first synthesis for a new speaker,
or after service restart).
Measured on RTX 5060 8GB (fp32): get_emb count drops 3->2 for a single
inference call, and the microbenchmarked forward costs ~33s mean (8s
reference, range 18-40s) - this cost scales with reference-audio length,
so the waste grows for longer prompts. Outputs are unaffected since the
second result was never consumed.
Co-authored-by: ardss <ardss@users.noreply.github.com>
The speaker cache-miss path decoded and resampled the reference audio
twice; the second call returns a bit-identical tensor to the audio_16k
already in scope (_load_and_cut_audio is deterministic), so reuse it,
as infer_v2.py already does at the equivalent spot. Saves one librosa
decode + truncation + resample per speaker cache miss.
Refs #796.
Two shapes returned by the preset handlers are rejected by Gradio, so
several ordinary interactions in the WebUI raised instead of no-oping.
`on_preset_save` returned a bare `gr.update()` when the name was empty.
Gradio serializes the no-argument form as the dict `{"__type__": "update"}`
-- its "skip" sentinel -- and `confirm_save_preset_from_modal` indexes that
result with `result[0]`/`result[1]`, so saving a preset without typing a
name raised `KeyError: 0` (index-tts#787). Return two updates instead, which
is what the sibling branches already do.
`on_preset_load` returned `{}` on its early-exit paths. Gradio wraps a lone
non-tuple value into a one-element list, so an empty dict fails its output
arity check -- "didn't return enough output values" -- for the 24-output
events this handler drives; that fires whenever the preset dropdown is
cleared, or a preset's audio files are gone. The sentinel is the correct
"update nothing" value: Gradio expands it to skip every output.
The two paths are covered by `tests/test_webui_handlers.py`, which reads
webui.py's AST rather than importing it (Gradio is an optional extra and
importing the module downloads checkpoints, so neither belongs in the
`not gpu` CI job).
`infer_generator()` parses `do_sample` out of `generation_kwargs` and then
never uses it — the call to `inference_speech()` passes a literal `True`:
indextts/infer_v2.py:536 do_sample = generation_kwargs.pop("do_sample", True)
indextts/infer_v2.py:589 do_sample=True,
indextts/infer_v2_5.py:731 do_sample = generation_kwargs.pop("do_sample", True)
indextts/infer_v2_5.py:780 do_sample=True,
In both files the parsed local is dead: those two lines are its only mentions.
Because the value is `pop`ped rather than left in `generation_kwargs`, passing
`do_sample=False` is swallowed silently instead of reaching the model, so greedy
decoding has never been reachable through the public `infer()` API.
The value does have somewhere to arrive: `inference_speech()` collects
`**hf_generate_kwargs` and forwards them to `GPT2InferenceModel.generate()`
(`indextts/gpt/model_v2.py:820`, `indextts/gpt/model_v2_5.py:779`), which is
where HuggingFace reads `do_sample`.
Default behaviour is unchanged — the `pop` default is still `True`, and only an
explicit `do_sample=False` now takes effect.
Fixes#577. Supersedes #726, which fixed the v2 site only; 2.5 is the current
default path and had the same defect.
Verified: `uv run --extra test pytest -m "not gpu"` — 151 passed, with two
`test_model_download_reachable` failures from HuggingFace CDN SSL resets that
reproduce on unmodified main.
Co-authored-by: nanaoto <10inspiral@gmail.com>
torchaudio 2.9 moved save() onto TorchCodec, whose compatibility shim
converts non-float32 input with a bare src.float() and no rescaling, then
treats the result as [-1, 1] audio. Every save site passed a PCM-scale
int16 tensor, so on 2.9+ roughly 99.99% of frames clipped to full scale
with no exception and no warning.
Add save_pcm_wav() in indextts/utils/common.py, which normalizes PCM-scale
input to float32 [-1, 1] and pins encoding="PCM_S"/bits_per_sample=16 on
the torchaudio versions that still honour them. Pinning matters: below 2.9
the WAV subtype is derived from the input dtype, so normalizing alone would
silently switch output from 16-bit PCM to 32-bit float. Above 2.9 the two
arguments are ignored and warn, so they are omitted there.
Route all six save sites in indextts/ and backends/trt/ through it.
Verified on torchaudio 2.7.1, 2.8.0, 2.9.0 and 2.11.0: identical 16-bit
PCM output, 0% clipped frames. Before the change 2.9.0 and 2.11.0 produced
only 3 distinct sample values with 99.9875% of frames at full scale.
Fixes#724
The Space at IndexTeam/IndexTTS-2.5-Demo now runs 2.5, but the Demos
column only pointed at the demo page and the ModelScope studio. Add a
Space badge next to them in all five badge-style READMEs, and a plain
[HF Space] link in README2.5_ZH.md, which uses plain links instead.
The 2.5 inference path exposes `use_gpt_latent`, but enabling it has never
worked: the fusion at `S_infer + latent` raises
RuntimeError: The size of tensor a (1706) must match the size of tensor b (853)
The two tensors are on different time bases by construction.
`EnhancedCodec.decode()` upsamples 2x (`indextts/codec/models.py:226`,
`F.interpolate(x, scale_factor=2, mode="nearest")`, with `downsample_scale=2` as
the constructor default and no override in the released config), while the GPT
latent stays at the code-token rate. 853 -> 1706 is exactly that factor.
This is 2.5-specific: 2.0 hardcodes `use_gpt_latent=True`
(`indextts/infer_v2.py:145`) and works, because it uses the maskgct codec, which
has no such upsampling step. When 2.5 switched codecs the latent path was left
wired to the old assumption.
Rather than guess an alignment, remove the option. The released 2.5 checkpoint
does carry six `gpt_layer` tensors, but that only records what the training run
built, not that this is a supported inference path — and if 2.5 had actually been
trained through this fusion, the lengths would agree. Any alignment added now
would be inferred rather than recovered from the training recipe, and getting it
wrong yields quietly degraded audio instead of a crash, which is worse.
No behaviour change for anyone: the parameter defaulted to False, and neither
`webui.py` nor `indextts/cli_v2.py` exposes it. `load_checkpoint2` iterates
`for key in model.models`, so the now-unbuilt `gpt_layer` weights are skipped
silently with no warning. `MyModel` keeps its `use_gpt_latent` argument, which
`backends/trt/export/*.py` still passes.
Also drops the `gpt_forward_time` counter, whose only accumulation site was
inside the removed block — it would otherwise print a constant `0.00` and read as
"this stage was free" rather than "this stage does not exist". 2.0 keeps its own.
Verified: 153 passed / 22 deselected, and a real MPS inference produces audio
with the default path untouched.
Co-authored-by: nanaoto <10inspiral@gmail.com>
* test: resolve temp paths in the CLI tests so they pass off Linux
These assertions compare a path the CLI printed against one built from
`tempfile.TemporaryDirectory()`. On Linux those agree; on macOS they do not,
because `/var` is a symlink to `/private/var` and the CLI resolves it:
AssertionError: 'model_dir = "/var/folders/.../models"'
not found in 'model_dir = "/private/var/folders/.../models"'
18 of 153 non-GPU tests failed that way. Windows has its own version of the same
problem — `RUNNER~1` 8.3 short names and drive-letter case — so this blocks CI on
any non-Linux runner.
Resolve at the point the temp directory is wrapped, which is what
`test_cli_v2_download.py` already does at its seven sites and why that file was
the only one passing. 129 call sites across the other four files; no behaviour
changes, the comparison just uses the same canonical form on both sides.
153 passed, 0 failed afterwards.
* ci: run the lightweight checks on Windows too
Everything platform-specific in this repo was unverified. `pyproject.toml`
carries three `sys_platform` markers — triton-windows gated to win32, and the
wetext / WeTextProcessing split on linux — and `cli_tests` branches three ways on
`sys.platform` for config and model-cache locations. CI only ever exercised the
Linux side of any of it. The triton-windows marker bug (#742, #749) was exactly
this shape: a Windows-only resolution failure that no amount of green Linux CI
could catch.
No GPU is needed. `-m "not gpu"` deselects the 22 GPU tests, and the one
remaining case that passes `--device cuda` runs against a hand-built
`SimpleNamespace` whose `is_available` is `lambda: True`
(`cli_tests/test_cli_v2_check.py:93-98`), so nothing looks for a real device.
`fail-fast: false` so a Windows failure still lets the Linux result through, and
the uv cache is enabled — the Windows job resolves a cu128 torch and there is no
reason to refetch it every run.
Blocking rather than advisory, because the previous commit fixed the path
assertions that would otherwise have failed there: 153 passed, 0 failed.
---------
Co-authored-by: nanaoto <10inspiral@gmail.com>
* webui: adapt precision and QwenEmotion loading to available VRAM
The WebUI hardcoded use_qwen_emo=True and tied precision to --fp16, which
defaults off, so a small card got the most expensive combination available.
Measured on a 4090, peak reserved memory for a short utterance:
fp32 + QwenEmotion 8.15 GB (the previous default)
bf16 + QwenEmotion 6.54 GB
bf16, no QwenEmotion 5.48 GB
Re-run under a hard per-process cap via set_per_process_memory_fraction, the
old default does complete on an 8 GB budget -- but only by peaking at exactly
8.00 GB, i.e. with no headroom for the display output and driver overhead a
real 8 GB card also has to fund. The new path leaves ~2.5 GB spare and still
runs under a 6 GB cap (5.44 GB peak).
So below 10 GB, enable half precision and skip QwenEmotion, reusing the
threshold infer_v2_5.py already applies for text chunking. --qwen_emo forces it
back on for someone who knows their card can take it.
Skipping that model removes emotion-control-from-text, so gate the UI on it
too: mode 3 disappears from the experimental choices, its examples are filtered
out, and a preset saved with it resets to the default with a warning. Without
this gating, selecting mode 3 raises "use_emo_text=True requires QwenEmotion"
from infer_v2_5.py at inference time.
Verified all three configurations start and report the intended decision, with
process memory matching the table above.
* Gate use_bf16 on torch.cuda.is_bf16_supported() and reset is_experimental on fallback
Co-authored-by: nanaoto <19526637+nanaoto@users.noreply.github.com>
---------
Co-authored-by: nanaoto <10inspiral@gmail.com>
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: nanaoto <19526637+nanaoto@users.noreply.github.com>
The recipe has landed, so the links no longer need to reference the pull
request. Verified https://recipes.vllm.ai/IndexTeam/IndexTTS-2.5 returns 200
and titles itself "IndexTeam/IndexTTS-2.5 | vLLM Recipes".
Seven occurrences across the English, Chinese, Japanese, Spanish and Arabic
READMEs.
Co-authored-by: nanaoto <10inspiral@gmail.com>
The 2.5 row's only Demos entry was index-tts2-5.github.io, which serves the
technical report rather than something you can talk to. Add the hosted studio
next to it so a reader can try the model without installing anything.
Verified https://modelscope.cn/studios/IndexTeam/IndexTTS-2.5 returns 200 and
titles itself "IndexTTS-2.5". English, Chinese, Japanese, Spanish and Arabic.
Co-authored-by: nanaoto <10inspiral@gmail.com>
* fix: repair Linux installs and README commands that fail on a clean machine
Verified on a Linux GPU box using a fresh .venv and a separate UV_CACHE_DIR,
since a warm cache hides every one of these.
pyproject.toml:
- triton-windows had no platform marker, so `uv sync --extra accel` (or
--all-extras) aborted on Linux: the package only publishes win_amd64 wheels.
Linux already gets `triton` as a torch dependency.
- Replace `no-build-isolation-package` with `extra-build-dependencies`. That
mode uses `.venv` itself as the build environment, and a fresh `.venv` has no
setuptools yet, so deepspeed failed with `ModuleNotFoundError: No module
named 'setuptools'` on any clean machine. Injecting torch with
`match-runtime` keeps the CUDA-enabled build inputs while leaving isolation
(and its setuptools) in place. It also lets flash-attn resolve a prebuilt
wheel from its own torch version and C++ ABI flag instead of invoking nvcc.
README (and the zh/ja/es/ar translations):
- `examples/09.wav` does not exist in the demo space and is not in
cases.jsonl; the on-demand download fetches `voice_09.wav`.
- `huggingface-hub` dropped its `cli` extra, so the install line warned.
- Example audio is fetched by the WebUI only, not by `infer_v2_5.py`, whose
default --prompt_wav is `examples/voice_01.wav`. Say so and give a one-liner
to fetch it.
- Drop `uv run indextts/infer_v2.py`: it is a benchmark loop hardcoded to
`checkpoints/`, so with the documented `checkpoints_2` layout it dies on
`vocab_file checkpoints/bpe.model does not exist`.
- `use_emo_text=True` needs `use_qwen_emo=True` at construction, which the
init example omits.
webui.py: title and arXiv badge were hardcoded to IndexTTS2, so the 2.5 UI
announced itself as 2 and linked the 2.0 paper.
* docs: install huggingface-hub without extras
`hf-xet` is an unconditional base dependency of huggingface-hub on x86_64,
arm64 and aarch64, not just an extra, so `[hf_xet]` selected nothing that the
plain package does not already pull in. The plain install still provides the
`hf`, `huggingface-cli` and `tiny-agents` executables.
Applies to the remaining seven translations and archived READMEs too, which
still carried the `[cli,hf_xet]` form whose `cli` extra no longer exists.
* Potential fix for pull request finding
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
* Potential fix for pull request finding
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
---------
Co-authored-by: nanaoto <10inspiral@gmail.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
* feat: default webui to IndexTTS-2.5
- webui.py: --version default 2 -> 2.5. The v2 default existed only
because the empty-checkpoints auto-download fallback was implemented
for v2 alone; 2.5 just exited. Now both versions auto-download their
own repo (IndexTTS-2 / IndexTTS-2.5) when checkpoints are incomplete.
- webui.py: version-aware required_files. v2 checks bpe.model etc.;
2.5 checks codec.pth + multilingual_zh_ja_yue_char_del.tiktoken
(bpe.model is v2-only, 2.5 uses tiktoken).
- model_download.py: ensure_config_available takes version, downloads
config.yaml from the matching repo instead of hardcoded IndexTTS-2.
- README: 2.5 launch is now 'uv run webui.py' (zero flags).
* Fix ensure_config_available version validation and sync localized READMEs
Co-authored-by: nanaoto <19526637+nanaoto@users.noreply.github.com>
---------
Co-authored-by: nanaoto <10inspiral@gmail.com>
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: nanaoto <19526637+nanaoto@users.noreply.github.com>
- Add translated READMEs: docs/README_ja.md, docs/README_es.md,
docs/README_ar.md (structure, code blocks and tables kept identical
to the English README)
- Add WebUI locales: ja_JP.json, es_ES.json, ar_SA.json (110 keys
each, covering every i18n key used in webui.py)
- Fill 3 missing keys in en_US.json (删除/情感向量/语言)
- Add language switcher links to the README headers
Co-authored-by: nanaoto <10inspiral@gmail.com>
- Add light/dark transparent logo variants, switch via <picture>
prefers-color-scheme so the header no longer shows a white box in
GitHub dark mode
- Add stars / arXiv / Discord badges under the language links
- Reorganize both READMEs into a clear section order: header intro,
model zoo, news, demos, getting started, usage, evaluation,
community, citation, acknowledgements, license
- Fix BibTeX author separators (use 'and') and complete the 2.5
author list in the zh README
- Add missing duration_factor and vLLM sections to the zh README
- Drop git-lfs steps (examples download on demand from HF/ModelScope)
- Remove uv advocacy block