# Deploy VibeVoice 1.5B (long-form) to irv-ml1. # # Builds the image locally from groxaxo/VibeVoice-FastAPI1 via docker # buildx git URL context. ~12 min cold build (CUDA 12.8 + torch 2.8 + # flash-attn). First start downloads VibeVoice-1.5B (~7 GB) into the # bind-mounted HF cache. Generous /healthz wait deadline accommodates # both. # # Usage: # scripts/elway irv-ml1 --playbook playbooks/deploy-vibevoice.yaml # # Idempotent — every step is creates-/when-gated; rerun is safe. vars: compose_dir: /opt/docker/compose/vibevoice voices_dir: /worktank/vibevoice/voices cache_dir: /worktank/vibevoice/cache host_port: "8194" steps: # ── host-side dirs ────────────────────────────────────────────────── - name: Ensure /worktank/vibevoice root exists (one-time, sudo) shell: mkdir -p /worktank/vibevoice sudo: true creates: /worktank/vibevoice - name: Chown /worktank/vibevoice to lkraven shell: chown lkraven:lkraven /worktank/vibevoice sudo: true when: '[ "$(stat -c %U /worktank/vibevoice)" != lkraven ]' - name: Ensure voices dir exists shell: mkdir -p {{ voices_dir }} creates: "{{ voices_dir }}" - name: Ensure cache dir exists shell: mkdir -p {{ cache_dir }} creates: "{{ cache_dir }}" - name: Ensure compose dir exists shell: mkdir -p {{ compose_dir }} creates: "{{ compose_dir }}" # ── deploy compose files ──────────────────────────────────────────── - name: Upload compose.yaml upload: src: stacks/vibevoice/compose.yaml dest: "{{ compose_dir }}/compose.yaml" mode: "0644" - name: Seed .env from template (only if absent) upload: src: stacks/vibevoice/.env.example dest: "{{ compose_dir }}/.env" mode: "0644" when: "[ ! -f {{ compose_dir }}/.env ]" # ── build + bring up ──────────────────────────────────────────────── - name: docker compose build (~12 min first time; cached after) # --progress=plain stops the carriage-return TUI from littering the log # with garbled redraws. The grep drops pip's Downloading / Collecting / # Requirement / progress-bar lines (~25% of the log) while keeping # buildkit step transitions, DONE/CACHED/ERROR markers, and any apt / # build-stage messages worth seeing. set -o pipefail so a build # failure isn't swallowed by the grep return code. shell: | set -o pipefail cd {{ compose_dir }} && docker compose build --progress=plain 2>&1 \ | grep -vE '^#[0-9]+ [0-9.]+ (Downloading|Collecting|Requirement|Using cached|Installing collected|Successfully (installed|built)|Saved /|━|Resolved|Prepared|Built)' - name: docker compose up -d shell: cd {{ compose_dir }} && docker compose up -d - name: Wait for /health to respond (allow ~20 min for model download + warmup) shell: | for i in $(seq 1 240); do curl -sf -o /dev/null --max-time 3 http://localhost:{{ host_port }}/health && exit 0 sleep 5 done exit 1 changed_when: "false" verify: - name: /health returns 200 shell: curl -sf -o /dev/null http://localhost:{{ host_port }}/health changed_when: "false" - name: /v1/audio/voices returns OpenAI list-shape JSON # Response is `{"object":"list","data":[...]}` per OpenAI's # /v1/audio/voices contract. `data` may be empty when no voice # files are seeded yet at /worktank/vibevoice/voices/ — the # wrapper is still healthy. Don't grep for sample voice names; # the seed set is user-controlled and a fresh install legitimately # ships with zero voices. shell: | curl -sf http://localhost:{{ host_port }}/v1/audio/voices \ | python3 -c "import json,sys; d=json.load(sys.stdin); assert d.get('object')=='list' and isinstance(d.get('data'), list)" changed_when: "false" - name: Container is running shell: docker inspect vibevoice --format '{{.State.Status}}' | grep -q running changed_when: "false"