diff --git a/scripts/r49-corpus/ship-voice-adapter.sh b/scripts/r49-corpus/ship-voice-adapter.sh new file mode 100755 index 0000000..968d421 --- /dev/null +++ b/scripts/r49-corpus/ship-voice-adapter.sh @@ -0,0 +1,76 @@ +#!/usr/bin/env bash +# Ship an r49 voice LoRA from the training box to the voices seat's adapter store. +# +# scripts/r49-corpus/ship-voice-adapter.sh +# e.g. ... ~/r49-runs/mccarthy-4b-pairs-3ep checkpoint-900 lv-mccarthy-4b-v1 +# +# Run from nh3-dev. gx10 and fv-ml1 both hold only authorized_keys, so neither can +# INITIATE to the other -- the relay through this box is the path, and it costs nothing +# because the WAN hop happens once either way. +# +# ⚠ THE SHA VERIFICATION IS THE POINT, not decoration. "copied the adapter" is a claim +# with no honest form but a read-back, and an adapter that is silently truncated or +# half-written loads without complaint and serves a subtly different voice. The digest is +# taken at the SOURCE, after the local hop, and at the DESTINATION, and any mismatch +# aborts before the seat is ever told about the file. +# +# Deliberately does NOT touch the compose file or restart the seat. Registering the +# adapter is a reviewed edit to stacks/voices-seat/compose.yaml plus deploy-stack.sh, +# because that file is where the gate verdict and its caveats are written down for +# whoever reads it next. +set -euo pipefail + +RUN=${1:?usage: ship-voice-adapter.sh } +CKPT=${2:?missing checkpoint} +NAME=${3:?missing adapter name} +SRC_HOST=${SRC_HOST:-infra-ops@10.100.50.60} # pfi-gx10 +DST_HOST=${DST_HOST:-infra-ops@10.251.50.54} # fv-ml1 +DST_ROOT=${DST_ROOT:-/tank/aimodels/voice-adapters} +STAGE=$(mktemp -d) +trap 'rm -rf "$STAGE"' EXIT + +say(){ echo "[ship $(date +%H:%M:%S)] $*"; } + +say "source $SRC_HOST:$RUN/checkpoints/$CKPT" +say "dest $DST_HOST:$DST_ROOT/$NAME" + +# The two files vLLM actually loads. A checkpoint dir also holds optimizer state and RNG +# that must NOT be shipped -- they are large, useless to the seat, and would make the +# destination digest disagree with any future re-ship. +FILES="adapter_config.json adapter_model.safetensors" + +say "digest at SOURCE" +SRC_SUMS=$(ssh -o BatchMode=yes "$SRC_HOST" "cd '$RUN/checkpoints/$CKPT' && sha256sum $FILES") +echo "$SRC_SUMS" | sed 's/^/ /' + +say "pull to local stage" +for f in $FILES; do + rsync -a "$SRC_HOST:$RUN/checkpoints/$CKPT/$f" "$STAGE/$f" +done +STAGE_SUMS=$(cd "$STAGE" && sha256sum $FILES) +if [ "$SRC_SUMS" != "$STAGE_SUMS" ]; then + say "ABORT: digest changed on the source->local hop"; echo "$STAGE_SUMS"; exit 1 +fi +say " hop 1 verified" + +say "push to dest" +ssh -o BatchMode=yes "$DST_HOST" "mkdir -p '$DST_ROOT/$NAME'" +for f in $FILES; do + rsync -a "$STAGE/$f" "$DST_HOST:$DST_ROOT/$NAME/$f" +done +DST_SUMS=$(ssh -o BatchMode=yes "$DST_HOST" "cd '$DST_ROOT/$NAME' && sha256sum $FILES") +if [ "$SRC_SUMS" != "$DST_SUMS" ]; then + say "ABORT: digest differs at the destination"; echo "$DST_SUMS"; exit 1 +fi +say " hop 2 verified -- byte-identical to the checkpoint on gx10" + +say "destination listing" +ssh -o BatchMode=yes "$DST_HOST" "ls -la '$DST_ROOT/$NAME'" | sed 's/^/ /' +cat <