Refactor: Remove unused Whisper variants
This commit removes the `whisper_variant` and `whisper_hotwords_variant` fields from the `run_id` generation and the `print_meta_summary` function. These variants are no longer used as the project is shifting focus to LLM-based generation. The `run_full_case.rs` example has also been updated to reflect this change.
This commit is contained in:
Executable
+217
@@ -0,0 +1,217 @@
|
||||
#!/usr/bin/env bash
|
||||
# Start doctate-server with the chosen ASR backend (whisper or canary).
|
||||
#
|
||||
# Coordinates the GPU host (minerva.lan) so the chosen ASR container is
|
||||
# the only heavy GPU resident:
|
||||
# - Whisper (~1.5-3 GB) coexists with Ollama (~3-4 GB) on the RTX 3060.
|
||||
# - Canary (~9.7 GB) does NOT — Ollama must be unloaded first, else
|
||||
# Canary OOMs on its last few MB of model weights.
|
||||
# See docs/canary.md §2.6 for the VRAM accounting.
|
||||
#
|
||||
# Flow:
|
||||
# 1. cargo build -p doctate-server (fail fast, BEFORE touching minerva)
|
||||
# 2. ssh minerva.lan: docker compose stop the OTHER ASR stack
|
||||
# 3. (canary only) unload all Ollama models — must happen BEFORE the
|
||||
# canary container starts, otherwise canary loads its model into a
|
||||
# still-occupied GPU and OOMs in the last few MB. Order matters:
|
||||
# „start canary, then free VRAM" is too late — canary's model load
|
||||
# runs in the container background as soon as `compose up -d` returns.
|
||||
# 4. ssh minerva.lan: docker compose up -d the chosen stack — for
|
||||
# canary, the GPU is now guaranteed empty.
|
||||
# 5. poll the chosen backend until it answers, with progress pulses
|
||||
# and periodic container-log snapshots so a stuck start is visible
|
||||
# 6. exec cargo run -p doctate-server with ASR_BACKEND= exported
|
||||
#
|
||||
# Usage:
|
||||
# scripts/run-server.sh whisper
|
||||
# scripts/run-server.sh canary
|
||||
#
|
||||
# Env overrides (all optional):
|
||||
# MINERVA_HOST default minerva.lan
|
||||
# WHISPER_STACK default /opt/stacks/doctate-whisper
|
||||
# CANARY_STACK default /opt/stacks/doctate-canary
|
||||
# WHISPER_HOST_PORT default 9001
|
||||
# CANARY_HOST_PORT default 9002
|
||||
# OLLAMA_HOST_PORT default 11434
|
||||
# HEALTH_TIMEOUT_SECS default 600 (cold model-download room)
|
||||
# HEALTH_POLL_SECS default 3 (interval between probes)
|
||||
# LOG_SNAPSHOT_SECS default 30 (interval between log dumps)
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
MINERVA_HOST="${MINERVA_HOST:-minerva.lan}"
|
||||
WHISPER_STACK="${WHISPER_STACK:-/opt/stacks/doctate-whisper}"
|
||||
CANARY_STACK="${CANARY_STACK:-/opt/stacks/doctate-canary}"
|
||||
WHISPER_HOST_PORT="${WHISPER_HOST_PORT:-9001}"
|
||||
CANARY_HOST_PORT="${CANARY_HOST_PORT:-9002}"
|
||||
OLLAMA_HOST_PORT="${OLLAMA_HOST_PORT:-11434}"
|
||||
HEALTH_TIMEOUT_SECS="${HEALTH_TIMEOUT_SECS:-600}"
|
||||
HEALTH_POLL_SECS="${HEALTH_POLL_SECS:-3}"
|
||||
LOG_SNAPSHOT_SECS="${LOG_SNAPSHOT_SECS:-30}"
|
||||
|
||||
usage() {
|
||||
cat <<EOF >&2
|
||||
Usage: $0 <whisper|canary>
|
||||
|
||||
Coordinates the GPU host (\$MINERVA_HOST) so only the matching ASR
|
||||
container runs (and, for canary, Ollama is unloaded), then starts
|
||||
doctate-server locally with the matching ASR_BACKEND env var.
|
||||
See header comment for env overrides.
|
||||
EOF
|
||||
exit 64
|
||||
}
|
||||
|
||||
[[ $# -eq 1 ]] || usage
|
||||
case "$1" in
|
||||
whisper|canary) BACKEND="$1" ;;
|
||||
*) usage ;;
|
||||
esac
|
||||
|
||||
REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
cd "$REPO_ROOT/server"
|
||||
|
||||
# ---- Step 1: build before touching minerva. -------------------------------
|
||||
echo ">>> [1/6] cargo build -p doctate-server"
|
||||
cargo build -p doctate-server
|
||||
|
||||
# ---- Step 2: pick stop/start targets. -------------------------------------
|
||||
if [[ "$BACKEND" == "whisper" ]]; then
|
||||
STOP_STACK="$CANARY_STACK"
|
||||
START_STACK="$WHISPER_STACK"
|
||||
HEALTH_KIND="tcp"
|
||||
HEALTH_PORT="$WHISPER_HOST_PORT"
|
||||
HEALTH_DESC="TCP $MINERVA_HOST:$WHISPER_HOST_PORT"
|
||||
else
|
||||
STOP_STACK="$WHISPER_STACK"
|
||||
START_STACK="$CANARY_STACK"
|
||||
HEALTH_KIND="http"
|
||||
HEALTH_URL="http://$MINERVA_HOST:$CANARY_HOST_PORT/health"
|
||||
HEALTH_DESC="HTTP $HEALTH_URL"
|
||||
fi
|
||||
|
||||
echo ">>> [2/6] $MINERVA_HOST: stop $STOP_STACK"
|
||||
ssh "$MINERVA_HOST" "cd '$STOP_STACK' && docker compose stop"
|
||||
|
||||
# ---- Step 3 (canary only): unload Ollama models. --------------------------
|
||||
# Ollama keeps the active model warm for `keep_alive` seconds (300 in
|
||||
# settings.toml at the time of writing). When the operator switches to
|
||||
# canary in the middle of that window, the model is still resident and
|
||||
# canary OOMs on the last few MB. We trigger a synchronous unload via
|
||||
# the Ollama API: posting `keep_alive: 0` with an empty prompt drops
|
||||
# the model immediately and frees VRAM. (See ollama/ollama issue #2546.)
|
||||
unload_ollama_models() {
|
||||
local base="http://$MINERVA_HOST:$OLLAMA_HOST_PORT"
|
||||
local ps_file
|
||||
ps_file=$(mktemp)
|
||||
trap 'rm -f "$ps_file"' RETURN
|
||||
|
||||
if ! curl -fsS --max-time 5 "$base/api/ps" -o "$ps_file"; then
|
||||
echo " Ollama API unreachable at $base/api/ps — skipping unload" >&2
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Parse the JSON model list with python3 (avoid jq as a hard dep).
|
||||
local models
|
||||
models=$(python3 -c "
|
||||
import json, sys
|
||||
try:
|
||||
d = json.load(open('$ps_file'))
|
||||
for m in d.get('models', []):
|
||||
print(m.get('name', ''))
|
||||
except Exception:
|
||||
sys.exit(0)
|
||||
")
|
||||
if [[ -z "$models" ]]; then
|
||||
echo " Ollama: no models loaded — VRAM already free"
|
||||
return 0
|
||||
fi
|
||||
|
||||
while IFS= read -r name; do
|
||||
[[ -z "$name" ]] && continue
|
||||
echo " unloading: $name"
|
||||
curl -fsS --max-time 10 "$base/api/generate" \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d "{\"model\":\"$name\",\"prompt\":\"\",\"keep_alive\":0}" \
|
||||
-o /dev/null \
|
||||
|| echo " (warn: unload request for $name returned non-2xx)" >&2
|
||||
done <<< "$models"
|
||||
|
||||
# Give the GPU driver a moment to actually reclaim the pages before
|
||||
# canary starts allocating its own.
|
||||
sleep 2
|
||||
}
|
||||
|
||||
if [[ "$BACKEND" == "canary" ]]; then
|
||||
echo ">>> [3/6] freeing VRAM: unloading Ollama models BEFORE canary starts"
|
||||
unload_ollama_models
|
||||
else
|
||||
echo ">>> [3/6] (whisper) Ollama coexistence is fine — skipping unload"
|
||||
fi
|
||||
|
||||
# ---- Step 4: NOW start the chosen stack. ----------------------------------
|
||||
# For canary this is the critical moment — the GPU has just been freed
|
||||
# by step 3, and canary's container will allocate into an empty GPU
|
||||
# instead of fighting Ollama for the last 20 MB.
|
||||
echo ">>> [4/6] $MINERVA_HOST: start $START_STACK"
|
||||
ssh "$MINERVA_HOST" "cd '$START_STACK' && docker compose up -d"
|
||||
|
||||
# ---- Step 5: poll until the chosen backend answers. -----------------------
|
||||
echo ">>> [5/6] waiting for $HEALTH_DESC (up to ${HEALTH_TIMEOUT_SECS}s)"
|
||||
|
||||
probe_once() {
|
||||
if [[ "$HEALTH_KIND" == "tcp" ]]; then
|
||||
if (exec 3<>"/dev/tcp/$MINERVA_HOST/$HEALTH_PORT") 2>/dev/null; then
|
||||
exec 3<&- 3>&-
|
||||
return 0
|
||||
fi
|
||||
return 1
|
||||
else
|
||||
curl -fsS --max-time 5 "$HEALTH_URL" >/dev/null 2>&1
|
||||
fi
|
||||
}
|
||||
|
||||
start=$(date +%s)
|
||||
deadline=$(( start + HEALTH_TIMEOUT_SECS ))
|
||||
last_log_snapshot=0
|
||||
attempt=0
|
||||
while :; do
|
||||
if probe_once; then
|
||||
echo ""
|
||||
echo " backend is up after $(( $(date +%s) - start ))s"
|
||||
break
|
||||
fi
|
||||
|
||||
attempt=$(( attempt + 1 ))
|
||||
printf '.'
|
||||
|
||||
now=$(date +%s)
|
||||
if (( now >= deadline )); then
|
||||
echo ""
|
||||
echo "timeout waiting for $HEALTH_DESC after ${HEALTH_TIMEOUT_SECS}s" >&2
|
||||
echo "--- container logs (tail 40) ---" >&2
|
||||
ssh "$MINERVA_HOST" "cd '$START_STACK' && docker compose logs --tail 40" >&2 || true
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Periodic log snapshot: every LOG_SNAPSHOT_SECS, print elapsed time
|
||||
# and the last few log lines so the operator can SEE if the container
|
||||
# is loading, crashing, or stalled.
|
||||
if (( now - last_log_snapshot >= LOG_SNAPSHOT_SECS )); then
|
||||
echo ""
|
||||
elapsed=$(( now - start ))
|
||||
echo " [${elapsed}s elapsed] still waiting; recent container log:"
|
||||
ssh "$MINERVA_HOST" "cd '$START_STACK' && docker compose logs --tail 5 --no-log-prefix 2>&1" \
|
||||
| sed 's/^/ | /' \
|
||||
|| echo " (could not read logs)" >&2
|
||||
last_log_snapshot=$now
|
||||
fi
|
||||
|
||||
sleep "$HEALTH_POLL_SECS"
|
||||
done
|
||||
|
||||
# ---- Step 6: start the server. --------------------------------------------
|
||||
# dotenvy::dotenv() in server/src/main.rs does NOT override env vars that
|
||||
# are already set, so this export wins over any ASR_BACKEND= in .env.
|
||||
echo ">>> [6/6] starting doctate-server (ASR_BACKEND=$BACKEND)"
|
||||
export ASR_BACKEND="$BACKEND"
|
||||
exec cargo run -p doctate-server
|
||||
Reference in New Issue
Block a user