|
| 1 | +#!/bin/bash |
| 2 | +# ============================================================================ |
| 3 | +# 凡人修仙传 Trailer V2 — Cloud Deployment (Vast.ai) |
| 4 | +# |
| 5 | +# Automated workflow: |
| 6 | +# 1. Search for RTX 5090 / 4090 instance |
| 7 | +# 2. Create instance with PyTorch + diffusers |
| 8 | +# 3. Upload code + narration audio |
| 9 | +# 4. Run portrait generation + I2V shot generation |
| 10 | +# 5. Download results |
| 11 | +# 6. Destroy instance |
| 12 | +# |
| 13 | +# Prerequisites: |
| 14 | +# vastai set api-key YOUR_KEY |
| 15 | +# |
| 16 | +# Usage: |
| 17 | +# bash scripts/deploy_cloud.sh |
| 18 | +# ============================================================================ |
| 19 | + |
| 20 | +set -euo pipefail |
| 21 | + |
| 22 | +PROJECT_ROOT="$(cd "$(dirname "$0")/.." && pwd)" |
| 23 | +OUTPUT_DIR="${PROJECT_ROOT}/output/fanren-trailer-v2" |
| 24 | +REMOTE_WORK="/workspace/AnimateDiff" |
| 25 | + |
| 26 | +# Colors |
| 27 | +RED='\033[0;31m' |
| 28 | +GREEN='\033[0;32m' |
| 29 | +YELLOW='\033[1;33m' |
| 30 | +NC='\033[0m' |
| 31 | + |
| 32 | +echo -e "${GREEN}============================================${NC}" |
| 33 | +echo -e "${GREEN} 凡人修仙传 V2 — Cloud GPU Deployment${NC}" |
| 34 | +echo -e "${GREEN}============================================${NC}" |
| 35 | + |
| 36 | +# ---- Step 1: Check API key ---- |
| 37 | +if ! .venv/bin/vastai show user 2>/dev/null | grep -q "email"; then |
| 38 | + echo -e "${RED}Error: Vast.ai API key not set.${NC}" |
| 39 | + echo "Run: .venv/bin/vastai set api-key YOUR_KEY" |
| 40 | + exit 1 |
| 41 | +fi |
| 42 | +echo -e "${GREEN}✓ Vast.ai authenticated${NC}" |
| 43 | + |
| 44 | +# ---- Step 2: Find a GPU instance ---- |
| 45 | +echo -e "\n${YELLOW}Searching for RTX 5090/4090 instances...${NC}" |
| 46 | + |
| 47 | +# Search: RTX 5090 preferred, RTX 4090 fallback, >=24GB VRAM, cuda 12+ |
| 48 | +INSTANCE_ID="" |
| 49 | +for gpu_name in "RTX_5090" "RTX_4090"; do |
| 50 | + echo " Trying ${gpu_name}..." |
| 51 | + OFFERS=$(.venv/bin/vastai search offers \ |
| 52 | + "gpu_name=${gpu_name} num_gpus=1 gpu_ram>=24 inet_down>=500 disk_space>=80 reliability>0.95" \ |
| 53 | + --order "dph_total" --limit 3 --raw 2>/dev/null || true) |
| 54 | + |
| 55 | + if [ -n "$OFFERS" ] && [ "$OFFERS" != "[]" ]; then |
| 56 | + echo -e " ${GREEN}Found ${gpu_name} offers${NC}" |
| 57 | + echo "$OFFERS" | head -5 |
| 58 | + break |
| 59 | + fi |
| 60 | +done |
| 61 | + |
| 62 | +if [ -z "$OFFERS" ] || [ "$OFFERS" = "[]" ]; then |
| 63 | + echo -e "${RED}No suitable GPU instances found. Try again later.${NC}" |
| 64 | + exit 1 |
| 65 | +fi |
| 66 | + |
| 67 | +# ---- Step 3: Create instance ---- |
| 68 | +echo -e "\n${YELLOW}Creating instance...${NC}" |
| 69 | + |
| 70 | +# Use PyTorch nightly image with CUDA 12.8 |
| 71 | +OFFER_ID=$(echo "$OFFERS" | python3 -c "import sys,json; data=json.load(sys.stdin); print(data[0]['id'])" 2>/dev/null) |
| 72 | + |
| 73 | +if [ -z "$OFFER_ID" ]; then |
| 74 | + echo -e "${RED}Failed to parse offer ID. Please create instance manually:${NC}" |
| 75 | + echo " .venv/bin/vastai search offers 'gpu_name=RTX_4090 num_gpus=1 gpu_ram>=24'" |
| 76 | + echo " .venv/bin/vastai create instance <OFFER_ID> --image pytorch/pytorch:2.6.0-cuda12.6-cudnn9-devel --disk 80" |
| 77 | + echo "" |
| 78 | + echo "Then run the remote script manually:" |
| 79 | + echo " scp scripts/remote_generate.py <instance>:/workspace/" |
| 80 | + echo " ssh <instance> 'cd /workspace && python remote_generate.py'" |
| 81 | + exit 1 |
| 82 | +fi |
| 83 | + |
| 84 | +echo " Offer ID: ${OFFER_ID}" |
| 85 | +RESULT=$(.venv/bin/vastai create instance "${OFFER_ID}" \ |
| 86 | + --image "pytorch/pytorch:2.6.0-cuda12.6-cudnn9-devel" \ |
| 87 | + --disk 80 \ |
| 88 | + --onstart-cmd "pip install diffusers transformers accelerate safetensors soundfile pillow" \ |
| 89 | + --raw 2>/dev/null) |
| 90 | + |
| 91 | +INSTANCE_ID=$(echo "$RESULT" | python3 -c "import sys,json; print(json.load(sys.stdin).get('new_contract', ''))" 2>/dev/null) |
| 92 | +echo -e " ${GREEN}Instance created: ${INSTANCE_ID}${NC}" |
| 93 | + |
| 94 | +# ---- Step 4: Wait for instance to start ---- |
| 95 | +echo -e "\n${YELLOW}Waiting for instance to start...${NC}" |
| 96 | +for i in $(seq 1 60); do |
| 97 | + STATUS=$(.venv/bin/vastai show instance "${INSTANCE_ID}" --raw 2>/dev/null | python3 -c "import sys,json; print(json.load(sys.stdin).get('actual_status', 'unknown'))" 2>/dev/null || echo "unknown") |
| 98 | + if [ "$STATUS" = "running" ]; then |
| 99 | + echo -e " ${GREEN}Instance running!${NC}" |
| 100 | + break |
| 101 | + fi |
| 102 | + echo " Status: ${STATUS} (${i}/60)..." |
| 103 | + sleep 10 |
| 104 | +done |
| 105 | + |
| 106 | +# Get SSH details |
| 107 | +SSH_CMD=$(.venv/bin/vastai ssh-url "${INSTANCE_ID}" 2>/dev/null) |
| 108 | +echo " SSH: ${SSH_CMD}" |
| 109 | + |
| 110 | +# ---- Step 5: Upload files ---- |
| 111 | +echo -e "\n${YELLOW}Uploading code and narration audio...${NC}" |
| 112 | + |
| 113 | +# Create tarball of necessary files |
| 114 | +TARBALL="/tmp/animatediff_deploy.tar.gz" |
| 115 | +cd "${PROJECT_ROOT}" |
| 116 | +tar czf "${TARBALL}" \ |
| 117 | + animatediff/ \ |
| 118 | + scripts/produce_trailer_v2.py \ |
| 119 | + scripts/generate_portraits.py \ |
| 120 | + examples/fanren_trailer_v2.json \ |
| 121 | + output/fanren-trailer-v2/narration/ \ |
| 122 | + output/fanren-trailer-v2/storyboard_computed.json \ |
| 123 | + output/fanren-trailer-v2/shot_timing.json \ |
| 124 | + 2>/dev/null |
| 125 | + |
| 126 | +echo " Tarball: $(du -h ${TARBALL} | cut -f1)" |
| 127 | + |
| 128 | +# Upload via SCP (extract SSH host/port from SSH URL) |
| 129 | +.venv/bin/vastai copy "${TARBALL}" "${INSTANCE_ID}:/workspace/deploy.tar.gz" |
| 130 | +.venv/bin/vastai execute "${INSTANCE_ID}" "cd /workspace && tar xzf deploy.tar.gz && echo 'Upload complete'" |
| 131 | + |
| 132 | +# ---- Step 6: Run generation ---- |
| 133 | +echo -e "\n${YELLOW}Running Phase 2 on GPU...${NC}" |
| 134 | +.venv/bin/vastai execute "${INSTANCE_ID}" \ |
| 135 | + "cd /workspace && python scripts/produce_trailer_v2.py --phase 2" 2>&1 |
| 136 | + |
| 137 | +# ---- Step 7: Download results ---- |
| 138 | +echo -e "\n${YELLOW}Downloading generated shots...${NC}" |
| 139 | + |
| 140 | +.venv/bin/vastai execute "${INSTANCE_ID}" \ |
| 141 | + "cd /workspace && tar czf /workspace/results.tar.gz output/fanren-trailer-v2/portraits/ output/fanren-trailer-v2/shots/" |
| 142 | + |
| 143 | +.venv/bin/vastai copy "${INSTANCE_ID}:/workspace/results.tar.gz" "/tmp/results.tar.gz" |
| 144 | +cd "${PROJECT_ROOT}" && tar xzf /tmp/results.tar.gz |
| 145 | + |
| 146 | +echo -e "${GREEN}Downloaded portraits and shots to ${OUTPUT_DIR}${NC}" |
| 147 | + |
| 148 | +# ---- Step 8: Destroy instance ---- |
| 149 | +echo -e "\n${YELLOW}Destroying instance to stop billing...${NC}" |
| 150 | +.venv/bin/vastai destroy instance "${INSTANCE_ID}" |
| 151 | +echo -e "${GREEN}Instance destroyed.${NC}" |
| 152 | + |
| 153 | +# ---- Summary ---- |
| 154 | +echo -e "\n${GREEN}============================================${NC}" |
| 155 | +echo -e "${GREEN} Phase 2 Complete!${NC}" |
| 156 | +echo -e "${GREEN}============================================${NC}" |
| 157 | +echo "" |
| 158 | +echo "Portraits: ${OUTPUT_DIR}/portraits/" |
| 159 | +echo "Shots: ${OUTPUT_DIR}/shots/" |
| 160 | +echo "" |
| 161 | +echo "Next: Run Phase 3 locally:" |
| 162 | +echo " python scripts/produce_trailer_v2.py --phase 3" |
0 commit comments