mars: add KittenTTS voice provider (CPU-only, offline mini model)
tts.provider: kittentts, voice Luna, mini-0.8 model (80M params) per darman. CPU-only onnxruntime inference — no GPU on mars, validated ~1x realtime on a weaker dev box. Upstream's misaki[en]+spacy declaration is deliberately not honored (pulls torch→CUDA, 5.6 GB); a .pth-registered stub satisfies the dead misaki import, failing loudly if it is ever actually used. HF model cache pre-seeded from hash-pinned store paths so HF_HUB_OFFLINE=1 runs with zero boot-time network. Provisioner shaped by the Mnemosyne review: uv venv --clear, root never executes python from the venv, ownership to the container uid before anything imports, stamp checked against venv+model files.
This commit is contained in:
@@ -0,0 +1,34 @@
|
||||
# Runtime shim for KittenTTS on a CPU-only, disk-constrained host.
|
||||
#
|
||||
# kittentts/onnx_model.py opens with `from misaki import en, espeak` — a dead import:
|
||||
# every code path that produces audio from text goes through phonemizer-fork's
|
||||
# EspeakBackend (espeak-ng via espeakng-loader); misaki.en/espeak are never called.
|
||||
# If misaki[en] were installed for real, those two imports would be a hook for the
|
||||
# whole spacy → thinc → torch → CUDA chain (~5.6 GB verified). This stub registers
|
||||
# `misaki.en` and `misaki.espeak` as import-time-only placeholders instead.
|
||||
#
|
||||
# Written into site-packages by the derivation (see pkgs/kittentts-env.nix) under
|
||||
# `sitecustomize.py`-style auto-load — actually via a `kitten_tts_stub.py` + a `.pth`
|
||||
# pointing at it, so any Python process in this env gets the stub registered before
|
||||
# any kittentts import. If KittenTTS upstream ever starts USING misaki, this shim
|
||||
# will fail loudly at import of the missing attributes (better than silent distortion),
|
||||
# and the fix becomes "install the real misaki[en]" — a deliberate, reviewed change.
|
||||
import sys
|
||||
import types
|
||||
|
||||
_misaki = sys.modules.get("misaki")
|
||||
if _misaki is None:
|
||||
# Avoid registering a fake parent before the real misaki loads — the base misaki
|
||||
# (addict/regex only) is installed normally, so usually already here.
|
||||
import misaki # noqa: F401 (raises if base misaki is missing — loud, not silent)
|
||||
_misaki = sys.modules["misaki"]
|
||||
|
||||
if getattr(_misaki, "en", None) is None:
|
||||
_stub = types.ModuleType("misaki.en")
|
||||
sys.modules.setdefault("misaki.en", _stub)
|
||||
_misaki.en = _stub
|
||||
|
||||
if getattr(_misaki, "espeak", None) is None:
|
||||
_stub2 = types.ModuleType("misaki.espeak")
|
||||
sys.modules.setdefault("misaki.espeak", _stub2)
|
||||
_misaki.espeak = _stub2
|
||||
@@ -0,0 +1,69 @@
|
||||
# Pinned requirements for the KittenTTS side-venv on mars (validated freeze, CPU-only).
|
||||
#
|
||||
# DEPENDENCY DISCIPLINE (important — this is NOT the upstream dependency set):
|
||||
# `kittentts` declares `misaki[en]` + spacy, which transitively pull torch → the whole
|
||||
# CUDA runtime (~5.6 GB with nvidia-* wheels, verified in a clean install on this box).
|
||||
# NONE of that is reachable at runtime: onnx_model.py imports `from misaki import en, espeak`
|
||||
# at module load but generate() phonemizes exclusively through `phonemizer-fork` +
|
||||
# `espeakng-loader` (espeak-ng via EspeakBackend); misaki.en/espeak glyphs are parsed but
|
||||
# never called in the ONNX path. mars has no GPU and a modest disk budget, so:
|
||||
#
|
||||
# - install kittentts with --no-deps and enumerate its deps manually,
|
||||
# - install base `misaki` (NOT misaki[en]) — only addict/regex,
|
||||
# - the misaki.en import is satisfied by a stub registered in sitecustomize
|
||||
# (kitten-misaki-stub.py, written into site-packages by the derivation).
|
||||
#
|
||||
# Freeze captured 2026-09-19 from a verified CPU-only venv (python 3.13, i5-4460,
|
||||
# generation ~= 1x realtime with the mini model, HF_HUB_OFFLINE=1).
|
||||
#
|
||||
# kittentts itself comes from a GitHub release wheel (0.8.1), not PyPI (PyPI's 0.1.3 is
|
||||
# the old v0.1-lineage); see pkgs/kittentts-env.nix.
|
||||
addict==2.4.0
|
||||
anyio==4.15.1
|
||||
attrs==26.1.0
|
||||
babel==2.18.0
|
||||
certifi==2026.7.22
|
||||
cffi==2.1.1
|
||||
click==8.5.0
|
||||
cloudpickle==3.1.2
|
||||
csvw==4.1.0
|
||||
dlinfo==2.0.0
|
||||
docopt==0.6.2
|
||||
espeakng-loader==0.2.4
|
||||
filelock==4.0.1
|
||||
flatbuffers==25.12.19
|
||||
fsspec==2026.9.0
|
||||
h11==0.16.0
|
||||
hf-xet==1.6.0
|
||||
httpcore==1.0.9
|
||||
httpx==0.28.1
|
||||
huggingface-hub==1.32.0
|
||||
idna==3.20
|
||||
isodate==0.7.2
|
||||
joblib==1.6.0
|
||||
jsonschema-specifications==2025.9.1
|
||||
jsonschema==4.26.0
|
||||
language-tags==1.3.1
|
||||
misaki==0.9.4
|
||||
num2words==0.5.14
|
||||
numpy==2.5.3
|
||||
onnxruntime==1.30.0
|
||||
packaging==26.3
|
||||
phonemizer-fork==3.3.2
|
||||
protobuf==7.36.2
|
||||
pycparser==3.0
|
||||
pyparsing==3.3.2
|
||||
python-dateutil==2.9.0.post0
|
||||
pyyaml==6.0.3
|
||||
rdflib==7.6.0
|
||||
referencing==0.37.0
|
||||
regex==2026.9.10
|
||||
rfc3986==1.5.0
|
||||
rpds-py==2026.6.3
|
||||
segments==2.4.0
|
||||
six==1.17.0
|
||||
soundfile==0.14.0
|
||||
termcolor==3.3.0
|
||||
tqdm==4.70.1
|
||||
typing-extensions==4.16.0
|
||||
uritemplate==4.2.0
|
||||
Reference in New Issue
Block a user