aboutsummaryrefslogtreecommitdiffstats
path: root/scripts
diff options
context:
space:
mode:
Diffstat (limited to 'scripts')
-rwxr-xr-xscripts/bootstrap.sh132
-rwxr-xr-xscripts/clonar-voz.sh47
-rwxr-xr-xscripts/servidores.sh83
3 files changed, 262 insertions, 0 deletions
diff --git a/scripts/bootstrap.sh b/scripts/bootstrap.sh
new file mode 100755
index 0000000..9ba694e
--- /dev/null
+++ b/scripts/bootstrap.sh
@@ -0,0 +1,132 @@
+#!/usr/bin/env bash
+# Deja el repositorio en condiciones de ejecutarse: submódulos, parches,
+# binarios y modelos.
+#
+# Es idempotente: se puede volver a lanzar sin miedo. Y no copia modelos —son
+# más de 5 GB—, sino que los enlaza desde donde ya estén.
+set -euo pipefail
+
+cd "$(dirname "$0")/.."
+RAIZ="$PWD"
+
+# Repositorios ya clonados en el sistema, para no volver a bajar de la red ni
+# recompilar lo que ya está compilado.
+ESPEJO="${ASIST_ESPEJO:-$HOME/GIT-MIRRO}"
+HF="${ASIST_HF:-$HOME/HF}"
+
+azul() { printf '\033[36m%s\033[0m\n' "$*"; }
+verde() { printf '\033[32m%s\033[0m\n' "$*"; }
+aviso() { printf '\033[33m%s\033[0m\n' "$*" >&2; }
+malo() { printf '\033[31m%s\033[0m\n' "$*" >&2; }
+
+enlazar() { # origen destino descripción
+ local origen="$1" destino="$2" que="$3"
+ if [ ! -e "$origen" ]; then
+ aviso " no está $que: $origen"
+ return 1
+ fi
+ mkdir -p "$(dirname "$destino")"
+ if [ -L "$destino" ] || [ -e "$destino" ]; then
+ rm -rf "$destino"
+ fi
+ ln -s "$origen" "$destino"
+ verde " $que -> $(basename "$origen")"
+}
+
+# ---------------------------------------------------------------------------
+azul "1/5 Submódulos"
+# Si el repositorio está clonado al lado, se usa como referencia: llama.cpp son
+# 406 MB de objetos que no hace falta volver a descargar.
+for nombre in canary-rs qwentts.cpp llama.cpp; do
+ ruta="vendor/$nombre"
+ if [ -f "$ruta/.git" ] || [ -d "$ruta/.git" ]; then
+ continue
+ fi
+ if [ -d "$ESPEJO/$nombre/.git" ]; then
+ git -c protocol.file.allow=always submodule update --init \
+ --reference "$ESPEJO/$nombre" "$ruta"
+ else
+ git submodule update --init "$ruta"
+ fi
+done
+git submodule status | sed 's/^/ /'
+
+# ---------------------------------------------------------------------------
+azul "2/5 Parches y ficheros sueltos"
+# Los parches recogen los cambios locales sobre cada repositorio: sin ellos, el
+# ASR se comporta distinto al que se midió.
+for nombre in canary-rs qwentts.cpp llama.cpp; do
+ parche="$RAIZ/vendor/patches/$nombre.patch"
+ [ -s "$parche" ] || continue
+ (
+ cd "vendor/$nombre"
+ if git apply --check "$parche" 2>/dev/null; then
+ git apply "$parche"
+ verde " $nombre: parche aplicado"
+ elif git apply --reverse --check "$parche" 2>/dev/null; then
+ verde " $nombre: parche ya aplicado"
+ else
+ malo " $nombre: el parche no aplica; revísalo a mano"
+ fi
+ )
+done
+# Ficheros que sólo existían en el árbol de trabajo local y que un parche no
+# puede llevar. canary-rs no compila sin bench_live.rs: su Cargo.toml lo declara.
+if [ -d vendor/extra ]; then
+ for nombre in canary-rs qwentts.cpp; do
+ [ -d "vendor/extra/$nombre" ] || continue
+ cp -rn "vendor/extra/$nombre/." "vendor/$nombre/" 2>/dev/null || true
+ done
+ verde " ficheros sueltos copiados"
+fi
+
+# ---------------------------------------------------------------------------
+azul "3/5 Motores en C++"
+enlazar_o_construir() { # nombre ruta_build orden_de_construccion...
+ local nombre="$1" build="$2"; shift 2
+ if [ -e "vendor/$nombre/$build" ]; then
+ verde " $nombre ya está construido"
+ return
+ fi
+ if [ -e "$ESPEJO/$nombre/$build" ]; then
+ enlazar "$ESPEJO/$nombre/$(dirname "$build")" \
+ "$RAIZ/vendor/$nombre/$(dirname "$build")" "build de $nombre"
+ return
+ fi
+ aviso " $nombre sin construir. Construyendo (esto tarda)…"
+ ( cd "vendor/$nombre" && "$@" )
+}
+enlazar_o_construir llama.cpp build/bin/llama-server \
+ bash -c 'cmake -B build -DCMAKE_BUILD_TYPE=Release -DGGML_CUDA=ON && cmake --build build -j --target llama-server'
+enlazar_o_construir qwentts.cpp build/tts-server \
+ bash -c './buildcuda.sh'
+
+# ---------------------------------------------------------------------------
+azul "4/5 Modelos"
+# Se enlazan, no se copian: son más de 5 GB entre todos.
+enlazar "$HF/Qwen/Qwen3.5-2B.Q5_K_M.gguf" "$RAIZ/models/Qwen3.5-2B.Q5_K_M.gguf" "modelo del LLM" || true
+enlazar "$HF/Qwen/mmproj-BF16.gguf" "$RAIZ/models/mmproj-BF16.gguf" "proyector multimodal" || true
+for m in qwen-talker-1.7b-base-Q8_0.gguf qwen-tokenizer-12hz-Q8_0.gguf; do
+ enlazar "$ESPEJO/qwentts.cpp/models/$m" "$RAIZ/models/$m" "$m" || true
+done
+if [ ! -d vendor/canary-rs/models/canary-180m-flash-onnx ] \
+ && [ -d "$ESPEJO/canary-rs/models/canary-180m-flash-onnx" ]; then
+ enlazar "$ESPEJO/canary-rs/models/canary-180m-flash-onnx" \
+ "$RAIZ/vendor/canary-rs/models/canary-180m-flash-onnx" "modelo Canary" || true
+fi
+
+# ---------------------------------------------------------------------------
+azul "5/5 Voz de referencia"
+# El asistente habla con una voz clonada. Si no hay una preparada, se dice cómo
+# hacerla en vez de fallar: el resto ya funciona con una voz del modelo.
+if [ -f assets/voices/asistente.spk ]; then
+ verde " ya hay una voz en assets/voices/"
+else
+ aviso " sin voz clonada. Prepárala con:"
+ aviso " scripts/clonar-voz.sh <grabacion.wav> <transcripcion.txt>"
+ aviso " o quita la sección [tts.reference] de config/asistente.toml para"
+ aviso " usar una voz del propio modelo."
+fi
+
+echo
+azul "Listo. Comprueba con: cargo run --release -- check"
diff --git a/scripts/clonar-voz.sh b/scripts/clonar-voz.sh
new file mode 100755
index 0000000..e68215d
--- /dev/null
+++ b/scripts/clonar-voz.sh
@@ -0,0 +1,47 @@
+#!/usr/bin/env bash
+# Extrae los latentes de una voz a partir de una grabación, para que el
+# asistente hable con ella.
+#
+# scripts/clonar-voz.sh grabacion.wav transcripcion.txt [nombre]
+#
+# Produce assets/voices/<nombre>.{spk,rvq,txt}, que es lo que el asistente
+# registra en tts-server al arrancar. Se hace una vez: en cada arranque se
+# mandan los latentes ya extraídos y no la grabación.
+set -euo pipefail
+
+cd "$(dirname "$0")/.."
+
+WAV="${1:?uso: clonar-voz.sh <grabacion.wav> <transcripcion.txt> [nombre]}"
+TXT="${2:?falta la transcripción de la grabación}"
+NOMBRE="${3:-asistente}"
+
+CODEC=vendor/qwentts.cpp/build/qwen-codec
+TOKENIZER=models/qwen-tokenizer-12hz-Q8_0.gguf
+
+for f in "$CODEC" "$TOKENIZER" "$WAV" "$TXT"; do
+ [ -e "$f" ] || { echo "falta: $f" >&2; exit 1; }
+done
+
+SALIDA="assets/voices"
+mkdir -p "$SALIDA"
+
+echo "Extrayendo la voz de $WAV…"
+# --talker produce el embedding del hablante (.spk) y los códigos de
+# referencia (.rvq); con la transcripción, el servidor activa el clonado ICL,
+# que se parece bastante más al original que sólo el embedding.
+"$CODEC" --model "$TOKENIZER" --talker \
+ --input "$WAV" \
+ -o "$SALIDA/$NOMBRE"
+
+cp "$TXT" "$SALIDA/$NOMBRE.txt"
+
+echo
+echo "Listo:"
+ls -la "$SALIDA/$NOMBRE".{spk,rvq,txt}
+echo
+echo "Apunta config/asistente.toml a esta voz:"
+echo " [tts.reference]"
+echo " name = \"$NOMBRE\""
+echo " speaker = \"../$SALIDA/$NOMBRE.spk\""
+echo " codes = \"../$SALIDA/$NOMBRE.rvq\""
+echo " transcript = \"../$SALIDA/$NOMBRE.txt\""
diff --git a/scripts/servidores.sh b/scripts/servidores.sh
new file mode 100755
index 0000000..2e3b769
--- /dev/null
+++ b/scripts/servidores.sh
@@ -0,0 +1,83 @@
+#!/usr/bin/env bash
+# Arranca o para los dos servidores por separado, sin el asistente.
+#
+# Útil para desarrollar: cargar los modelos cuesta más de un minuto y así se
+# reinicia el binario de Rust las veces que haga falta sin volver a pagarlo.
+# El asistente detecta que ya están escuchando y los reutiliza.
+#
+# scripts/servidores.sh arrancar | parar | estado
+set -euo pipefail
+cd "$(dirname "$0")/.."
+
+LOGS="${ASIST_LOGS:-logs}"
+LLM_PUERTO="${ASIST_LLM_PUERTO:-8012}"
+TTS_PUERTO="${ASIST_TTS_PUERTO:-8013}"
+mkdir -p "$LOGS"
+
+vivo() { curl -sf -m 2 "http://127.0.0.1:$1/health" >/dev/null 2>&1; }
+
+esperar() { # puerto nombre segundos
+ local fin=$(( SECONDS + $3 ))
+ while [ $SECONDS -lt $fin ]; do
+ vivo "$1" && { echo " $2 listo"; return 0; }
+ sleep 1
+ done
+ echo " $2 NO respondió en $3 s; mira $LOGS/$2.log" >&2
+ return 1
+}
+
+arrancar() {
+ if vivo "$LLM_PUERTO"; then
+ echo " llama-server ya estaba arriba"
+ else
+ echo " arrancando llama-server…"
+ nohup vendor/llama.cpp/build/bin/llama-server \
+ --model models/Qwen3.5-2B.Q5_K_M.gguf \
+ --mmproj models/mmproj-BF16.gguf \
+ --host 127.0.0.1 --port "$LLM_PUERTO" \
+ --threads 10 --threads-batch 10 \
+ --batch-size 512 --ubatch-size 256 \
+ --gpu-layers 10 --split-mode layer --tensor-split 1 --main-gpu 0 \
+ --no-mmap --ctx-size 8192 --parallel 2 --cache-ram 6144 \
+ --rope-freq-base 1000000 --rope-freq-scale 0.25 \
+ --jinja --chat-template-file config/qwen35-no-think.jinja \
+ > "$LOGS/llama-server.log" 2>&1 &
+ fi
+
+ if vivo "$TTS_PUERTO"; then
+ echo " tts-server ya estaba arriba"
+ else
+ echo " arrancando tts-server…"
+ # --codec-chunk-dur 1.0 es lo que hace que el audio salga por bloques
+ # según se genera en vez de todo al final. Ver docs/RENDIMIENTO.md.
+ nohup vendor/qwentts.cpp/build/tts-server \
+ --model models/qwen-talker-1.7b-base-Q8_0.gguf \
+ --codec models/qwen-tokenizer-12hz-Q8_0.gguf \
+ --host 127.0.0.1 --port "$TTS_PUERTO" \
+ --lang spanish --codec-chunk-dur 1.0 \
+ > "$LOGS/tts-server.log" 2>&1 &
+ fi
+
+ esperar "$LLM_PUERTO" llama-server 240
+ esperar "$TTS_PUERTO" tts-server 240
+}
+
+parar() {
+ # SIGTERM, no SIGKILL: hay que darles ocasión de soltar la GPU.
+ pkill -TERM -f 'llama-server .*--port '"$LLM_PUERTO" 2>/dev/null && echo " llama-server parado" || true
+ pkill -TERM -f 'tts-server .*--port '"$TTS_PUERTO" 2>/dev/null && echo " tts-server parado" || true
+}
+
+estado() {
+ vivo "$LLM_PUERTO" && echo " llama-server arriba :$LLM_PUERTO" || echo " llama-server parado"
+ vivo "$TTS_PUERTO" && echo " tts-server arriba :$TTS_PUERTO" || echo " tts-server parado"
+ command -v nvidia-smi >/dev/null && \
+ nvidia-smi --query-gpu=memory.used,memory.total --format=csv,noheader | sed 's/^/ GPU: /'
+}
+
+case "${1:-estado}" in
+ arrancar|start) arrancar ;;
+ parar|stop) parar ;;
+ estado|status) estado ;;
+ *) echo "uso: $0 {arrancar|parar|estado}" >&2; exit 1 ;;
+esac