Files
Conal Mullan fd24349fe1 REMOVE: EchoMimicV3 — drift made it unusable, SoulX is the default (#81)
EchoMimicV3 was added earlier in this same unreleased cycle and never reached a
tagged release, so this removes it rather than deprecating it — no public API
changes.

Identity drift is why. On a controlled A/B (same photo, same 80s audio, same
544x736) it fell to 50% of frame-zero sharpness by 70s, glasses dissolving
around 55s and no recognisable face by 67s, where SoulX-FlashHead held 97%. The
failure is absorbing rather than gradual — each segment re-anchors on the
previous segment's output, so one bad segment poisons everything after it — and
colour correction recovers none of it because the damage is structural. That
implied a ~30s render ceiling which shaped the surrounding design.

The one capability SoulX lacks is text-prompt and CFG steering, and our own
docs already recorded from testing that the prompt is close to inert. Everything
else EchoMimicV3 offered, SoulX matches or beats: aspect preservation, motion
quality, ~3.7x cheaper, ~3-6x faster. The genuine loss is gesture and
upper-body motion, which nothing in the toolkit currently uses.

Also in this commit:
- Talking head docs, registry entries (tools and modal endpoints), cloud_gpu
  dispatch, modal-setup, NarratorPiP's comment and both env files repointed to
  soulx. cloud_gpu also gains a GPU tier for it, which echomimic3 never had --
  its jobs silently reported no cost estimate.
- The Unreleased Kiro changelog entry is dropped; it shipped in v0.19.0 and was
  flagged in-file for removal when this section was cut.

Findings worth keeping outlived the tool and moved into docs/soulx.md: the
warning against scoring talking heads with similarity metrics (two have now
misled — one ranked highest a variant with a visible eye defect, the other
plateaued straight through a total collapse), the volume-weights rationale, and
the image guidelines.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-31 11:41:35 +01:00

267 lines
11 KiB
Markdown

# Modal Cloud GPU Setup
Modal is the recommended cloud GPU provider for the toolkit's AI tools. It offers $30/month free compute on the Starter plan, fast cold starts, and scale-to-zero billing.
> **Fastest path:** Run `/setup` in Claude Code — it handles Modal installation, deployment, and `.env` configuration interactively. This doc is the reference for what `/setup` does under the hood, and for manual setup.
## Create a Modal Account
1. Go to [modal.com](https://modal.com/) and sign up
2. Choose the **Starter plan** — $30/month free compute, just requires a payment method
3. Typical toolkit usage is $1-2/month, well within the free allowance
4. All apps scale to zero — no charges when idle
## Install & Authenticate
```bash
uv sync --extra modal # Installs the Modal CLI into the toolkit's .venv
uv run modal setup # Opens browser to authenticate, saves token to ~/.modal.toml
uv run modal app list # Verify it works
```
### Windows prerequisites
Two Windows-specific issues will otherwise make the commands in this guide fail in ways
that look like toolkit bugs.
**Set `PYTHONIOENCODING=utf-8` before deploying.** Modal's progress output contains
characters the default `cp1252` console codec can't encode, so `modal deploy` aborts
mid-build with `'charmap' codec can't encode characters in position ...`. The build
itself is fine — only the printing fails. Make it permanent:
```powershell
[Environment]::SetEnvironmentVariable('PYTHONIOENCODING','utf-8','User')
```
Reopen your terminal afterwards so new processes inherit it.
**Make sure `python3` isn't the Microsoft Store stub.** A default Windows install puts
`C:\Users\<you>\AppData\Local\Microsoft\WindowsApps` ahead of your real Python on
`PATH`. That directory holds zero-byte App Execution Aliases which fail with
`Permission denied` — and since this toolkit's docs and commands all invoke
`python3 tools/...`, everything breaks. Check what resolves:
```powershell
(Get-Command python3).Source
```
If it points into `WindowsApps`, either turn off the `python.exe` / `python3.exe`
aliases in *Settings → Apps → App execution aliases*, or move your Python directory
ahead of `WindowsApps` in your user `PATH`.
**FFmpeg** is not bundled on Windows and several tools need it. If `winget install
Gyan.FFmpeg` fails with a corrupted-source error, download
`ffmpeg-release-essentials.zip` from https://www.gyan.dev/ffmpeg/builds/, extract it,
and add its `bin\` directory to `PATH`.
## Deploy Tools
Each AI tool has its own Modal app. Deploy only what you need, or deploy all of them — idle apps cost nothing.
```bash
# Speech generation (most commonly used)
uv run modal deploy docker/modal-qwen3-tts/app.py
# Image generation & editing
uv run modal deploy docker/modal-flux2/app.py
uv run modal deploy docker/modal-image-edit/app.py
uv run modal deploy docker/modal-upscale/app.py
# Music generation
uv run modal deploy docker/modal-music-gen/app.py
# Video processing
uv run modal deploy docker/modal-sadtalker/app.py
uv run modal deploy docker/modal-propainter/app.py
# Talking head, diffusion-based. Weights live in a Volume, so populate it FIRST
# (one-off, ~15GB, ~5 min) or the app deploys with nothing to load.
uv run modal run docker/modal-soulx/app.py::populate_weights
uv run modal deploy docker/modal-soulx/app.py
# Video generation (see the LTX-2 prerequisites note below)
uv run modal deploy docker/modal-ltx2/app.py
```
**Deploy them one at a time.** Modal rate-limits `AppCreate`; launching several
`modal deploy` calls in parallel makes most of them fail with
`App create rate limit exceeded`. Image builds happen server-side, so serial
deploys cost you nothing extra in compute — only wall-clock.
**GPU tier gates some tools.** Most apps request an `A10G`, which works on the free
tier. Two request A100-class GPUs and fail at deploy time with
`Please add a payment method to use A100-40GB GPU functions` until a card is on the
account: `image-edit` (A100) and `ltx2` (A100-80GB). The image still gets built and
cached before that error, so re-deploying after adding a payment method is fast.
`ltx2` additionally needs a Modal secret named `huggingface-token` for its gated
weights — it fails on the missing secret *before* it ever reaches the GPU check.
Each deploy prints an endpoint URL like:
```
https://yourname--video-toolkit-qwen3-tts-ttsengine-generate.modal.run
```
Save each URL to your `.env` file:
```bash
# Add to .env (replace with your actual URLs from deploy output)
MODAL_QWEN3_TTS_ENDPOINT_URL=https://yourname--video-toolkit-qwen3-tts-...modal.run
MODAL_FLUX2_ENDPOINT_URL=https://yourname--video-toolkit-flux2-...modal.run
MODAL_IMAGE_EDIT_ENDPOINT_URL=https://yourname--video-toolkit-image-edit-...modal.run
MODAL_UPSCALE_ENDPOINT_URL=https://yourname--video-toolkit-upscale-...modal.run
MODAL_MUSIC_GEN_ENDPOINT_URL=https://yourname--video-toolkit-music-gen-...modal.run
MODAL_SADTALKER_ENDPOINT_URL=https://yourname--video-toolkit-sadtalker-...modal.run
MODAL_SOULX_ENDPOINT_URL=https://yourname--video-toolkit-soulx-...modal.run
MODAL_DEWATERMARK_ENDPOINT_URL=https://yourname--video-toolkit-dewatermark-...modal.run
MODAL_LTX2_ENDPOINT_URL=https://yourname--video-toolkit-ltx2-...modal.run
```
When an endpoint label exceeds Modal's length limit, Modal truncates it and appends a
hash — `dewatermark` typically becomes `...-dewatermark-de-3e6418.modal.run`. Copy the
URL Modal actually prints; don't reconstruct it from the pattern. Note that the deploy
log wraps long URLs across lines, so check for a `(label truncated)` marker and rejoin
the pieces.
> **Tip:** `/setup` automates this — it runs each deploy, parses the URL, and writes it to `.env` for you.
## Cloudflare R2 (Recommended)
R2 is free file storage that bridges your local machine and cloud GPUs. Without it, tools fall back to free upload services (slower, less reliable).
**R2 free tier:** 10GB storage, 10 million operations/month, zero egress fees.
See the R2 section in `/setup`, or configure manually:
1. Sign up at [dash.cloudflare.com](https://dash.cloudflare.com/)
2. Go to R2 Object Storage → Create bucket (name it `video-toolkit`)
3. Create an API token: R2 → Manage R2 API Tokens → Object Read & Write
4. Add to `.env`:
```
R2_ACCOUNT_ID=your_account_id
R2_ACCESS_KEY_ID=your_access_key_id
R2_SECRET_ACCESS_KEY=your_secret_access_key
R2_BUCKET_NAME=video-toolkit
```
## Use the Tools
All cloud GPU tools accept `--cloud modal`:
```bash
# AI voiceover
uv run tools/qwen3_tts.py --text "Hello world" --speaker Ryan --output hello.mp3 --cloud modal
# AI image generation
uv run tools/flux2.py --prompt "A sunset over mountains" --output sunset.png --cloud modal
# AI image editing
uv run tools/image_edit.py --input photo.jpg --style cyberpunk --cloud modal
# AI upscaling
uv run tools/upscale.py --input photo.jpg --output photo_4x.png --cloud modal
# AI music generation (acemusic cloud API is now default — no Modal needed)
uv run tools/music_gen.py --preset corporate-bg --duration 60 --output bg.mp3
# Or use Modal: uv run tools/music_gen.py --preset corporate-bg --duration 60 --output bg.mp3 --cloud modal
# Talking head from portrait + audio
uv run tools/sadtalker.py --image portrait.png --audio voiceover.mp3 --output talking.mp4 --cloud modal
uv run tools/soulx.py --image portrait.png --audio voiceover.mp3 --output talking.mp4
# Watermark removal
uv run tools/dewatermark.py --input video.mp4 --region 1080,660,195,40 --output clean.mp4 --cloud modal
```
## Tools & Costs
| Tool | Backend | Use Case | Est. Cost |
|------|---------|----------|-----------|
| `qwen3_tts` | Qwen3-TTS | AI speech generation | ~$0.005-0.02 |
| `flux2` | FLUX.2 Klein | AI image generation | ~$0.01-0.03 |
| `image_edit` | Qwen-Image-Edit | AI image editing, style transfer | ~$0.02-0.05 |
| `upscale` | RealESRGAN | AI image upscaling (2x/4x) | ~$0.005-0.02 |
| `music_gen` | ACE-Step 1.5 | AI music generation | Free (acemusic) / ~$0.02-0.10 (Modal) |
| `sadtalker` | SadTalker | Talking head video | ~$0.05-0.30 |
| `soulx` | SoulX-FlashHead 1.3B Pro | Talking head video, aspect-preserving | ~$0.0024 per second of output |
| `dewatermark` | ProPainter | AI video inpainting | ~$0.05-0.50 |
All apps use A10G GPUs (24GB VRAM) except `image_edit` which uses A100 for its 25GB model.
### Weight storage
Most apps **bake** their model weights into the image at build time. `soulx` is the
exception: it keeps its ~15GB in a **Modal Volume**, which is why it needs the one-off
`populate_weights` run above before its first deploy.
The split is measured, not stylistic. Cold start and generation speed are the same either
way; what differs is rebuild time after a dependency change — measured at 1.8-8.2s on a
volume against 79-385s baked, because any invalidated layer re-downloads everything below it. Apps that
still change often earn a volume; settled ones don't need one.
**Volumes are optional and free at this scale.** Modal charges $0.09/GiB/month for volume
storage with **1 TiB/month included free**, so `soulx`'s 14.7GB costs nothing — it is
~2.6% of the free allowance. There is no bill either way, which is precisely why the choice
comes down to rebuild speed versus having one self-contained artifact rather than to cost.
Every app can be built either way. `soulx` defaults to a volume; the rest bake by
default. See `docs/soulx.md` for the full
comparison.
## Cold Starts
First request after idle triggers a cold start while Modal loads the model:
| Tool | Cold Start | Warm Request |
|------|-----------|--------------|
| `qwen3_tts` | ~60-90s | ~5-15s |
| `flux2` | ~25-30s | ~1-3s |
| `image_edit` | ~5-8min | ~15-20s |
| `upscale` | ~25-30s | ~3-5s |
| `music_gen` | ~60-90s | ~10-30s |
| `sadtalker` | ~45-60s | ~30-60s |
| `soulx` | ~15s + ~600s first-call torch.compile | 6.4-7.9x realtime |
| `dewatermark` | ~60-70s | varies by video length |
After 60 seconds of no requests, containers scale back to zero. No charges while idle.
## Monitoring & Billing
```bash
# Check what's running (Tasks column should be 0 when idle)
uv run modal app list
# Check today's spend
uv run modal billing report --for today --json
# View container logs
uv run modal app logs video-toolkit-upscale
# Verify your setup
uv run tools/verify_setup.py
```
## Architecture
Each tool has its own Modal app (`docker/modal-*/app.py`), deployed independently:
- **One app per tool** — independent scaling, GPU assignment, and lifecycle
- **Web endpoints** — HTTP POST via `@modal.fastapi_endpoint`, no `modal` pip dependency needed on the client
- **R2 file transfer** — large results upload to Cloudflare R2 (if configured), otherwise base64
- **Scale to zero** — `scaledown_window=60` means containers shut down after 1 minute idle
The client-side abstraction lives in `tools/cloud_gpu.py`, which routes `call_cloud_endpoint()` to either `_call_runpod()` (submit + poll) or `_call_modal()` (synchronous POST).
## RunPod (Alternative)
RunPod is also supported as a fallback provider. Use `--cloud runpod` on any tool.
| Aspect | Modal | RunPod |
|--------|-------|--------|
| **Free tier** | $30/mo compute | None (pay-as-you-go) |
| **Setup** | `modal deploy` | `--setup` flag per tool |
| **Cold start** | Faster (cached layers) | Slower (Docker pull) |
| **Invocation** | Synchronous POST | Async submit + poll |
| **Auth** | Token optional for web endpoints | `RUNPOD_API_KEY` required |
See [runpod-setup.md](runpod-setup.md) for RunPod-specific instructions.