Files
LTX-2/modal/app.py
indigo b69eedbd54 Add Modal test environment for SCAIL-2 training/inference
A free-tier-friendly Modal app to validate the SCAIL-2 code path on real
Linux/GPU without a local GPU or the 19B checkpoint.

- modal/checks.py: device-aware (CPU/GPU auto) consolidation of the Phase 1-4
  plumbing checks using tiny random-init models + synthetic data (no checkpoint,
  no dataset): driving concat, zero-init patchify_proj widening (output-preserving),
  a FlexibleStrategy driving+mask training step + loss, and inference conditioning
  assembly. Passes on CPU locally.
- modal/app.py: builds the workspace via `uv sync` (skips CUDA-only ltx-kernels;
  attention falls back to SDPA). Functions: verify (CPU, ~free), smoke (T4, cents),
  train (A10G/A100, paid — runs the real trainer against a checkpoint + data on the
  scail-data Volume).
- modal/README.md: free-tier setup (modal setup), run commands, cost table, the
  scale-up path, and the expected preprocessed dataset layout.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-12 09:37:27 +08:00

85 lines
2.9 KiB
Python

"""Modal app for testing the SCAIL-2 LTX-2 training/inference path.
Free-tier friendly: `verify` runs on CPU and `smoke` on a cheap T4, both using
tiny random-init models + synthetic data (no checkpoint, no dataset) so a full run
costs pennies. `train` is the scale-up entrypoint that runs the real trainer once
you upload a checkpoint + preprocessed data to the `scail-data` Volume -- that one
uses a real GPU and is NOT free.
Setup (once):
pip install modal && modal setup
Run:
modal run modal/app.py::verify # CPU plumbing check (~free)
modal run modal/app.py::smoke # same checks on a T4 GPU (cents)
modal run modal/app.py::train --config-rel configs/scail_animation_lora.yaml
"""
import subprocess
from pathlib import Path
import modal
REPO = Path(__file__).parent.parent
# Build the workspace once into the image. ltx-kernels (CUDA-compiled) is excluded
# from the workspace and not needed -- attention falls back to SDPA.
image = (
modal.Image.debian_slim(python_version="3.11")
.apt_install("git")
.pip_install("uv")
.env({"UV_LINK_MODE": "copy", "UV_PROJECT_ENVIRONMENT": "/root/LTX-2/.venv"})
.add_local_dir(
str(REPO),
"/root/LTX-2",
copy=True,
ignore=[".git", ".venv", "**/__pycache__", "**/*.pyc", "outputs", "wandb", "**/.pytest_cache"],
)
.run_commands("cd /root/LTX-2 && uv sync --package ltx-trainer")
)
app = modal.App("scail-ltx2", image=image)
# Persistent volume for the (large) checkpoint + preprocessed dataset used by `train`.
data_volume = modal.Volume.from_name("scail-data", create_if_missing=True)
_UV_PY = ["uv", "run", "--package", "ltx-trainer", "python"]
def _run(args: list[str]) -> None:
subprocess.run([*_UV_PY, *args], cwd="/root/LTX-2", check=True)
@app.function(timeout=1800)
def verify() -> None:
"""Run the SCAIL-2 plumbing checks on CPU (near-free)."""
_run(["modal/checks.py"])
@app.function(gpu="T4", timeout=1800)
def smoke() -> None:
"""Run the same checks on a T4 GPU to validate the CUDA path (cents)."""
_run(["modal/checks.py"])
@app.function(gpu="A10G", timeout=60 * 60 * 6, volumes={"/data": data_volume})
def train(config_rel: str = "configs/scail_animation_lora.yaml") -> None:
"""Run the real SCAIL trainer. NOT free -- needs a real checkpoint + data.
Upload your base checkpoint, Gemma text encoder, and preprocessed data to the
``scail-data`` Volume (mounted at /data), and point the config's paths at /data.
For the full 19B model use a bigger GPU (e.g. gpu="A100") and expect real cost.
"""
_run(["packages/ltx-trainer/scripts/train.py", config_rel])
data_volume.commit()
@app.local_entrypoint()
def main(target: str = "verify", config_rel: str = "configs/scail_animation_lora.yaml") -> None:
if target == "smoke":
smoke.remote()
elif target == "train":
train.remote(config_rel)
else:
verify.remote()