ee35fcd0a9
Two issues from the first deploy attempt: 1) Build failure (real): linker errors on s2.cpp's CUDA build — undefined references to cuMemSetAccess, cuDeviceGet, etc. These are CUDA Driver API symbols (in libcuda.so), not Runtime API (libcudart.so). The driver lib is provided by NVIDIA's container runtime at RUN time, not BUILD time. Fix: nvidia/cuda:devel images ship a stubs library at /usr/local/cuda/lib64/stubs/libcuda.so that provides the symbols for linking but is non-runnable. Adding that path via LIBRARY_PATH + CMAKE_LIBRARY_PATH lets the linker resolve while leaving runtime unchanged (real libcuda.so comes from the driver mount). 2) Verify false positive: the /v1/tts verify step's last command was `rm -f "$out"` — which always exits 0. This made the shell's final exit code 0 regardless of whether curl/file/grep succeeded, so verify reported OK even when nothing was running on host_port. Fix: `set -e` at top + trap-based cleanup. Failures now propagate; the rm still runs on either path via EXIT trap.
137 lines
5.4 KiB
YAML
137 lines
5.4 KiB
YAML
# Deploy fish-cpp (Fish s2-pro via s2.cpp + GGML CUDA inference) to irv-ml1.
|
|
#
|
|
# Builds the image locally — multi-stage CUDA devel base (CMake + s2.cpp
|
|
# compile, ~10 min cold) → CUDA runtime base + binary + python shim.
|
|
# Pre-pulls rodrigomt/s2-pro-gguf weights (q6_k default, ~5 GB) into
|
|
# the bind-mounted weights dir.
|
|
#
|
|
# Usage:
|
|
# scripts/elway irv-ml1 --playbook playbooks/deploy-fish-cpp.yaml
|
|
#
|
|
# Idempotent — every step is creates-/when-gated; rerun is safe.
|
|
|
|
vars:
|
|
compose_dir: /opt/docker/compose/fish-cpp
|
|
references_dir: /worktank/fish-cpp/references
|
|
weights_dir: /worktank/fish-cpp/weights
|
|
host_port: "8199"
|
|
weights_repo: rodrigomt/s2-pro-gguf
|
|
default_quant: s2-pro-q6_k.gguf
|
|
|
|
steps:
|
|
# ── host-side dirs ──────────────────────────────────────────────────
|
|
|
|
- name: Ensure /worktank/fish-cpp root exists (one-time, sudo)
|
|
shell: mkdir -p /worktank/fish-cpp
|
|
sudo: true
|
|
creates: /worktank/fish-cpp
|
|
|
|
- name: Chown /worktank/fish-cpp to lkraven
|
|
shell: chown -R lkraven:lkraven /worktank/fish-cpp
|
|
sudo: true
|
|
when: "[ \"$(stat -c %U /worktank/fish-cpp)\" != \"lkraven\" ]"
|
|
|
|
- name: Ensure references dir exists
|
|
shell: mkdir -p {{ references_dir }}
|
|
creates: "{{ references_dir }}"
|
|
|
|
- name: Ensure weights dir exists
|
|
shell: mkdir -p {{ weights_dir }}
|
|
creates: "{{ weights_dir }}"
|
|
|
|
- name: Ensure compose dir exists
|
|
shell: mkdir -p {{ compose_dir }}
|
|
creates: "{{ compose_dir }}"
|
|
|
|
# ── deploy build context ────────────────────────────────────────────
|
|
# s2.cpp is built INSIDE the docker image, but the Dockerfile + shim
|
|
# need to be present in the compose dir so `docker compose build`
|
|
# can find them.
|
|
|
|
- name: Upload compose.yaml
|
|
upload:
|
|
src: stacks/fish-cpp/compose.yaml
|
|
dest: "{{ compose_dir }}/compose.yaml"
|
|
mode: "0644"
|
|
|
|
- name: Upload Dockerfile
|
|
upload:
|
|
src: stacks/fish-cpp/Dockerfile
|
|
dest: "{{ compose_dir }}/Dockerfile"
|
|
mode: "0644"
|
|
|
|
- name: Upload server.py (FastAPI shim)
|
|
upload:
|
|
src: stacks/fish-cpp/server.py
|
|
dest: "{{ compose_dir }}/server.py"
|
|
mode: "0644"
|
|
|
|
- name: Seed .env from template (only if absent)
|
|
upload:
|
|
src: stacks/fish-cpp/.env.example
|
|
dest: "{{ compose_dir }}/.env"
|
|
mode: "0644"
|
|
when: "[ ! -f {{ compose_dir }}/.env ]"
|
|
|
|
# ── pre-pull weights ────────────────────────────────────────────────
|
|
# q6_k + tokenizer.json (~5 GB total). Same one-shot
|
|
# python:3.12-slim + huggingface_hub.snapshot_download + hf_transfer
|
|
# pattern we've used for fish-s2, voxtral, etc. Idempotent on rerun
|
|
# via `creates:` on the model file.
|
|
|
|
- name: Pre-pull rodrigomt/s2-pro-gguf weights (q6_k + tokenizer, ~5 GB)
|
|
shell: |
|
|
docker run --rm --user 1000:1000 \
|
|
-e HOME=/tmp/h -e HF_HUB_ENABLE_HF_TRANSFER=1 \
|
|
-v {{ weights_dir }}:/dest \
|
|
python:3.12-slim sh -c 'set -e; mkdir -p /tmp/h /tmp/pip /tmp/site; PIP_CACHE_DIR=/tmp/pip pip install --quiet --target /tmp/site huggingface_hub hf_transfer; PYTHONPATH=/tmp/site python -c "from huggingface_hub import snapshot_download; snapshot_download(repo_id=\"{{ weights_repo }}\", local_dir=\"/dest\", allow_patterns=[\"{{ default_quant }}\",\"tokenizer.json\"])"'
|
|
creates: "{{ weights_dir }}/{{ default_quant }}"
|
|
|
|
# ── build + bring up ────────────────────────────────────────────────
|
|
|
|
- name: docker compose build (~10 min first time; CUDA toolchain + s2.cpp compile)
|
|
shell: |
|
|
set -o pipefail
|
|
cd {{ compose_dir }} && docker compose build 2>&1 \
|
|
| grep -vE '^#[0-9]+ |^ => |^=> |Collecting|Downloading|Requirement|Using cached|Installing collected|Successfully (installed|built)|━'
|
|
|
|
- name: docker compose up -d
|
|
shell: cd {{ compose_dir }} && docker compose up -d
|
|
|
|
- name: Wait for /v1/health to respond
|
|
shell: |
|
|
for i in $(seq 1 60); do
|
|
curl -sf -o /dev/null --max-time 3 http://localhost:{{ host_port }}/v1/health && exit 0
|
|
sleep 5
|
|
done
|
|
exit 1
|
|
changed_when: "false"
|
|
|
|
verify:
|
|
- name: /v1/health returns 200 + reports model loaded
|
|
shell: |
|
|
curl -sf http://localhost:{{ host_port }}/v1/health \
|
|
| python3 -c "import json,sys; d=json.load(sys.stdin); assert d.get('status')=='ok' and d.get('model')"
|
|
changed_when: "false"
|
|
|
|
- name: /v1/tts returns a real WAV (POST with text body)
|
|
# `set -e` so curl/file/grep failures actually propagate. The
|
|
# previous version put `rm -f` as the last command, which always
|
|
# exits 0 — masking real failures (verify reported OK even when
|
|
# nothing was running on host_port). Trap-based cleanup runs the
|
|
# rm even on failure.
|
|
shell: |
|
|
set -e
|
|
out=$(mktemp --suffix=.wav)
|
|
trap 'rm -f "$out"' EXIT
|
|
curl -sf -X POST http://localhost:{{ host_port }}/v1/tts \
|
|
-H 'Content-Type: application/json' \
|
|
-d '{"text":"Verify."}' \
|
|
-o "$out" --max-time 60
|
|
file -b "$out" | grep -q '^RIFF.*WAVE'
|
|
changed_when: "false"
|
|
|
|
- name: Container is running
|
|
shell: docker inspect fish-cpp --format '{{.State.Status}}' | grep -q running
|
|
changed_when: "false"
|