Ctrl K
ComunidadGuías…

setup.sh

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
#!/usr/bin/env bash
#
# First-time setup for the PolyTTS server.
# Creates a venv, installs dependencies, and downloads models.
#
# PyTorch (MPS on Mac, CUDA on Linux/Windows): uses the same HuggingFace
# models — setup downloads them below. MLX (Apple Silicon only): uses a
# different 8-bit model, downloaded automatically when you run with MLX.
#
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
cd "$SCRIPT_DIR"

# ── Colours ──────────────────────────────────────────────────────────────────
bold=$(tput bold 2>/dev/null || true)
reset=$(tput sgr0 2>/dev/null || true)
green=$(tput setaf 2 2>/dev/null || true)
red=$(tput setaf 1 2>/dev/null || true)
yellow=$(tput setaf 3 2>/dev/null || true)

info()  { echo "${bold}${green}==> ${reset}${bold}$*${reset}"; }
warn()  { echo "${bold}${yellow}==> ${reset}${bold}$*${reset}"; }
fail()  { echo "${bold}${red}==> ${reset}${bold}$*${reset}"; exit 1; }

# ── Pre-flight checks ───────────────────────────────────────────────────────
info "Checking prerequisites…"

arch=$(uname -m)
echo "  Architecture: $arch"

# Python 3.10–3.12 (the VoxCPM engine caps at <3.13; MLX/Qwen run on 3.12 too).
if ! command -v python3 &>/dev/null; then
    fail "python3 not found. Install Python 3.10–3.12 first."
fi
py_version=$(python3 -c 'import sys; print(f"{sys.version_info.major}.{sys.version_info.minor}")')
py_major=$(echo "$py_version" | cut -d. -f1)
py_minor=$(echo "$py_version" | cut -d. -f2)
if (( py_major != 3 || py_minor < 10 || py_minor > 12 )); then
    fail "Python 3.10–3.12 required (VoxCPM caps <3.13) — detected $py_version."
fi
echo "  Python: $py_version"

# ── Virtual environment ──────────────────────────────────────────────────────
if [[ -d venv ]] && [[ ! -x venv/bin/python3 ]] && [[ ! -x venv/bin/python ]]; then
    warn "Existing virtual environment looks stale — recreating it."
    rm -rf venv
fi

if [[ -d venv ]]; then
    info "Virtual environment already exists — skipping creation."
else
    info "Creating virtual environment…"
    python3 -m venv venv
fi

if [[ -x venv/bin/python3 ]]; then
    VENV_PYTHON="venv/bin/python3"
elif [[ -x venv/bin/python ]]; then
    VENV_PYTHON="venv/bin/python"
else
    fail "Virtual environment was created, but no Python interpreter was found in venv/bin/."
fi
VENV_PIP="venv/bin/pip"

# ── Install dependencies ─────────────────────────────────────────────────────
info "Installing Python dependencies (PyTorch backend; same models for MPS and CUDA)…"
"$VENV_PIP" install --upgrade pip -q
"$VENV_PIP" install -r requirements.txt -q

# MLX is Apple Silicon only; optional for PyTorch+MPS/CUDA
if [[ "$arch" == "arm64" ]] && [[ "$(uname -s)" == "Darwin" ]]; then
    info "Installing MLX backend (Apple Silicon)…"
    "$VENV_PIP" install -r requirements-mlx.txt -q
fi
echo "  Done."

# ── Livestack residency manager (polycore + shared_py) ───────────────────────
# On the PyTorch/CUDA runtime the server REQUIRES the polycore residency seam and
# its compiled shared_py planner (server.py: `_MANAGER_PATH = RUNTIME != "mlx"`,
# which imports `polycore` at module load). Both live in the livestack monorepo.
# Install them EDITABLE so the venv resolves them by package name at their current
# location — never a hardcoded path that goes stale when the repo moves. (The old
# breakage was a hand-written polycore.pth pointing at a since-moved directory.)
# Skipped under MLX (Apple Silicon), which runs the standalone residency path.
install_livestack_residency() {
    if [[ "$arch" == "arm64" ]] && [[ "$(uname -s)" == "Darwin" ]]; then
        echo "  MLX runtime — standalone residency; polycore not required."
        return
    fi
    local LIVESTACK_DIR="${LIVESTACK_DIR:-$HOME/livestack}"
    if [[ ! -d "$LIVESTACK_DIR/polycore" ]]; then
        warn "polycore not found under LIVESTACK_DIR=$LIVESTACK_DIR — point LIVESTACK_DIR at your livestack checkout and re-run. The CUDA server cannot start without it."
        return
    fi
    # Prefer uv (this venv may be uv-managed and lack pip); fall back to venv pip.
    local INSTALL=("$VENV_PIP" install)
    command -v uv &>/dev/null && INSTALL=(uv pip install --python "$VENV_PYTHON")
    info "Installing livestack residency (editable) from $LIVESTACK_DIR…"
    "${INSTALL[@]}" -e "$LIVESTACK_DIR/polycore" --no-deps
    [[ -d "$LIVESTACK_DIR/node-py" ]] && "${INSTALL[@]}" -e "$LIVESTACK_DIR/node-py" --no-deps
    # shared_py is a Rust/pyo3 extension — build it into THIS venv with maturin.
    "${INSTALL[@]}" maturin
    ( cd "$LIVESTACK_DIR/shared-py" \
        && env -u CONDA_PREFIX VIRTUAL_ENV="$SCRIPT_DIR/venv" PYO3_PYTHON="$VENV_PYTHON" \
           "$VENV_PYTHON" -m maturin develop --release )
    "$VENV_PYTHON" -c "import polycore, shared_py; print('  polycore + shared_py OK')"
}
install_livestack_residency

# ── Download models ──────────────────────────────────────────────────────────
info "Downloading PyTorch models (used for both MPS and CUDA)…"
mkdir -p models

download_model() {
    local repo="$1"
    local dest="models/$(basename "$repo")"
    if [[ -d "$dest" ]]; then
        echo "  $dest already exists — skipping."
        return
    fi
    echo "  Downloading $repo …"
    "$VENV_PYTHON" -c "
from huggingface_hub import snapshot_download
snapshot_download('$repo', local_dir='$dest')
"
    echo "  Saved to $dest"
}

# 1.7B model (default)
download_model "Qwen/Qwen3-TTS-12Hz-1.7B-Base"

# 0.6B model (optional)
echo ""
read -rp "${bold}Download the smaller 0.6B model too? [y/N] ${reset}" choice
if [[ "$choice" == "y" || "$choice" == "Y" ]]; then
    download_model "Qwen/Qwen3-TTS-12Hz-0.6B-Base"
fi

# ── Verify imports ───────────────────────────────────────────────────────────
info "Verifying key imports…"
"$VENV_PYTHON" -c "
import torch; print(f'  torch {torch.__version__}')
from qwen_tts import Qwen3TTSModel; print('  qwen_tts OK')
import soundfile; print('  soundfile OK')
import fastapi; print('  fastapi OK')
"

# The dots engine is import-fragile in one specific way: dots.tts refuses to load
# when torch and torchaudio disagree on their MINOR version, and nothing else in
# this venv cares, so the drift only shows up as "the dots voices 503". Assert the
# pair HERE, at install time, rather than letting it surface at first synthesis.
# Not fatal — a box that only serves qwen/voxcpm is still a working polytts.
if [[ "$arch" != "arm64" || "$(uname -s)" != "Darwin" ]]; then
    "$VENV_PYTHON" -c "
import torch, torchaudio
tm = torch.__version__.split('+')[0].rsplit('.', 1)[0]
am = torchaudio.__version__.split('+')[0].rsplit('.', 1)[0]
assert tm == am, f'torch {torch.__version__} / torchaudio {torchaudio.__version__} minors differ — dots.tts will not import'
import dots_tts.runtime  # noqa: F401
print('  dots_tts OK (torch/torchaudio pair matched)')
" || warn "dots.tts is not importable in this venv — the 'dots' engine will fail; qwen/voxcpm/cosyvoice are unaffected. Check that torch and torchaudio share a minor version."
fi
if [[ "$arch" == "arm64" ]] && [[ "$(uname -s)" == "Darwin" ]]; then
    "$VENV_PYTHON" -c "
import mlx.core; print(f'  mlx {mlx.core.__version__}')
from mlx_audio.tts.utils import load_model; print('  mlx_audio OK')
"
fi

# ── Done ─────────────────────────────────────────────────────────────────────
echo ""
info "Setup complete!"
echo ""
if [[ "$arch" == "arm64" ]] && [[ "$(uname -s)" == "Darwin" ]]; then
    echo "  Start the server (MLX backend — default on Apple Silicon):"
    echo "    ${bold}./run.sh${reset}"
    echo ""
    echo "  Or use the PyTorch backend (MPS):"
    echo "    ${bold}POLYTTS_RUNTIME=pytorch ./run.sh${reset}"
else
    echo "  Start the server (PyTorch backend; uses CUDA if available):"
    echo "    ${bold}POLYTTS_RUNTIME=pytorch ./run.sh${reset}"
fi
echo ""
echo "  Test it:"
echo "    ${bold}curl -X POST http://localhost:8100/tts \\${reset}"
echo "      ${bold}-H 'Content-Type: application/json' \\${reset}"
echo "      ${bold}-d '{\"text\": \"Hello world\", \"pack_id\": \"sc2-kerrigan-infested\"}' \\${reset}"
echo "      ${bold}--output test.wav${reset}"
echo ""