diff --git a/.agents/skills/seed-ssim-references/SKILL.md b/.agents/skills/seed-ssim-references/SKILL.md index 527b76ef9..0b1a2f78a 100644 --- a/.agents/skills/seed-ssim-references/SKILL.md +++ b/.agents/skills/seed-ssim-references/SKILL.md @@ -150,10 +150,12 @@ modal run fastvideo/tests/modal/ssim_test.py \ Env prefix rationale (parity with CI; see `.buildkite/pipeline.yml:1-3` and `.buildkite/scripts/pr_test.sh:62-83`): - `IMAGE_VERSION=py3.12-latest`: pins the Modal image tag to the same one CI - uses. Without this, `ssim_test.py:17` falls back to `latest`, which on - GHCR is built from `Dockerfile.python3.10` — different Python, torch, and - flash-attn wheel than CI's `py3.12-latest` (`infra-build-image.yml:51-67`, - `_template-build-image.yml:65-101`). + uses. The published `py3.12-latest` and `latest` tags point at Python 3.12 / + CUDA 12.6.3 / cu126; `py3.12-cuda12.6.3-latest` is the explicit alias for the + same image. CUDA 13 / cu130 is available under the explicit + `py3.12-cuda13.0.0-latest` tag. This tag policy comes from + `infra-build-image.yml`; the unparameterized `docker/Dockerfile` build itself + still defaults to CUDA 13 / cu130. - `BUILDKITE_REPO`/`BUILDKITE_COMMIT`/`BUILDKITE_PULL_REQUEST`: mirror what Buildkite exports. `ssim_test.py:38-46` bakes these into the image's `.env(...)` block; mismatched values can perturb in-container code paths diff --git a/.buildkite/pipeline.yml b/.buildkite/pipeline.yml index aefb41d12..80ef90601 100644 --- a/.buildkite/pipeline.yml +++ b/.buildkite/pipeline.yml @@ -251,7 +251,7 @@ steps: - "fastvideo/models/loader/**" - "fastvideo/tests/encoders/**" - "pyproject.toml" - - "docker/Dockerfile.python3.12" + - "docker/Dockerfile" config: command: "timeout 20m .buildkite/scripts/pr_test.sh" label: ":microscope: Encoder Tests" @@ -264,7 +264,7 @@ steps: - "fastvideo/models/loader/**" - "fastvideo/tests/vaes/**" - "pyproject.toml" - - "docker/Dockerfile.python3.12" + - "docker/Dockerfile" config: command: "timeout 20m .buildkite/scripts/pr_test.sh" label: ":microscope: VAE Tests" @@ -279,7 +279,7 @@ steps: - "fastvideo/layers/**" - "fastvideo/attention/**" - "pyproject.toml" - - "docker/Dockerfile.python3.12" + - "docker/Dockerfile" config: command: "timeout 15m .buildkite/scripts/pr_test.sh" label: ":microscope: Transformer Tests" @@ -290,7 +290,7 @@ steps: - path: - "fastvideo-kernel/**" - "pyproject.toml" - - "docker/Dockerfile.python3.12" + - "docker/Dockerfile" config: command: "timeout 15m .buildkite/scripts/pr_test.sh" label: ":microscope: Kernel Tests" @@ -303,7 +303,7 @@ steps: - ".buildkite/**" - ".github/**" - "pyproject.toml" - - "docker/Dockerfile.python3.12" + - "docker/Dockerfile" config: command: "timeout 15m .buildkite/scripts/pr_test.sh" label: ":microscope: Unit Tests" @@ -344,7 +344,7 @@ steps: - path: - "fastvideo/**/*.py" - "pyproject.toml" - - "docker/Dockerfile.python3.12" + - "docker/Dockerfile" config: command: "timeout 90m .buildkite/scripts/pr_test.sh" label: ":bar_chart: SSIM Tests" @@ -363,7 +363,7 @@ steps: - "fastvideo/pipelines/**" - "fastvideo/layers/lora/**" - "pyproject.toml" - - "docker/Dockerfile.python3.12" + - "docker/Dockerfile" config: command: "timeout 20m .buildkite/scripts/pr_test.sh" label: ":test_tube: LoRA Inference Tests" @@ -374,7 +374,7 @@ steps: - path: - "fastvideo/**" - "pyproject.toml" - - "docker/Dockerfile.python3.12" + - "docker/Dockerfile" config: command: "timeout 15m .buildkite/scripts/pr_test.sh" label: ":test_tube: Training Tests" @@ -385,7 +385,7 @@ steps: - path: - "fastvideo/training/*distillation_pipeline.py" - "pyproject.toml" - - "docker/Dockerfile.python3.12" + - "docker/Dockerfile" config: command: "timeout 15m .buildkite/scripts/pr_test.sh" label: ":test_tube: Distillation DMD Tests" @@ -397,9 +397,9 @@ steps: - "fastvideo/training/*self_forcing_distillation_pipeline.py" - "fastvideo/tests/training/self-forcing/**" - "pyproject.toml" - - "docker/Dockerfile.python3.12" + - "docker/Dockerfile" config: - command: "timeout 15m .buildkite/scripts/pr_test.sh" + command: "timeout 30m .buildkite/scripts/pr_test.sh" label: ":test_tube: Self-Forcing Tests" env: - TEST_TYPE=self_forcing @@ -408,7 +408,7 @@ steps: - path: - "fastvideo/**" - "pyproject.toml" - - "docker/Dockerfile.python3.12" + - "docker/Dockerfile" config: command: "timeout 15m .buildkite/scripts/pr_test.sh" label: ":test_tube: LoRA Training Tests" @@ -424,7 +424,7 @@ steps: - "fastvideo/**" - "fastvideo-kernel/**" - "pyproject.toml" - - "docker/Dockerfile.python3.12" + - "docker/Dockerfile" config: command: "timeout 15m .buildkite/scripts/pr_test.sh" label: ":test_tube: Training Tests VSA" @@ -440,7 +440,7 @@ steps: - "fastvideo-kernel/**" - "fastvideo/attention/backends/vmoba.py" - "pyproject.toml" - - "docker/Dockerfile.python3.12" + - "docker/Dockerfile" config: command: "timeout 15m .buildkite/scripts/pr_test.sh" label: ":test_tube: Inference Tests VMoBA" @@ -458,7 +458,7 @@ steps: - "fastvideo/tests/performance/**" - ".buildkite/performance-benchmarks/**" - "pyproject.toml" - - "docker/Dockerfile.python3.12" + - "docker/Dockerfile" config: command: "timeout 30m .buildkite/scripts/pr_test.sh" label: ":test_tube: Performance Tests" @@ -471,7 +471,7 @@ steps: - "fastvideo/entrypoints/cli/serve.py" - "fastvideo/tests/entrypoints/test_openai_api_integration.py" - "pyproject.toml" - - "docker/Dockerfile.python3.12" + - "docker/Dockerfile" config: command: "timeout 30m .buildkite/scripts/pr_test.sh" label: ":test_tube: API Server Tests" @@ -486,7 +486,7 @@ steps: - "fastvideo/models/dits/**" - "fastvideo/models/loader/**" - "pyproject.toml" - - "docker/Dockerfile.python3.12" + - "docker/Dockerfile" config: command: "timeout 30m .buildkite/scripts/pr_test.sh" label: ":test_tube: Train Framework Tests" @@ -498,7 +498,7 @@ steps: - "fastvideo/eval/**" - "fastvideo/tests/eval/**" - "pyproject.toml" - - "docker/Dockerfile.python3.12" + - "docker/Dockerfile" config: command: "timeout 90m .buildkite/scripts/pr_test.sh" label: ":test_tube: Eval Metrics Tests" diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 000000000..b88ad282e --- /dev/null +++ b/.dockerignore @@ -0,0 +1,31 @@ +# Build-context excludes: keep the context small and the `COPY . .` layer cache +# stable. Docker uploads everything here to the daemon and bakes it into a layer; +# without this, the 6.5 GB host .venv alone is shipped + cached on every build. +# +# IMPORTANT: do NOT ignore .git — fastvideo-kernel's build runs +# `git submodule update --init --recursive`, which needs the repo metadata. + +# Virtualenvs — the image builds its own /opt/venv +.venv/ +venv/ +env/ + +# Python caches & build/test/lint artifacts +**/__pycache__/ +*.py[cod] +*.egg-info/ +.eggs/ +.pytest_cache/ +.mypy_cache/ +.ruff_cache/ +.cache/ + +# Local run outputs / logs (not needed in the image) +outputs/ +wandb/ +*.log + +# Editor / OS cruft +.DS_Store +.idea/ +.vscode/ diff --git a/.github/workflows/_template-build-image.yml b/.github/workflows/_template-build-image.yml index cdf6fa16d..3fea4822a 100644 --- a/.github/workflows/_template-build-image.yml +++ b/.github/workflows/_template-build-image.yml @@ -24,10 +24,30 @@ on: required: false type: boolean default: true + mark_as_latest: + required: false + type: boolean + default: false + runner: + required: false + type: string + default: ubuntu-latest + architecture: + required: false + type: string + default: amd64 + push_by_digest: + required: false + type: boolean + default: false + digest_artifact_name: + required: false + type: string + default: '' jobs: build-and-push: - runs-on: ubuntu-latest + runs-on: ${{ inputs.runner }} permissions: contents: read packages: write @@ -35,6 +55,10 @@ jobs: steps: - name: Checkout code uses: actions/checkout@v4 + # The Docker context intentionally includes .git so the kernel build can + # initialize its pinned submodules. Do not copy the checkout token with it. + with: + persist-credentials: false - name: Free up disk space run: | @@ -73,6 +97,12 @@ jobs: registry: ghcr.io username: ${{ github.repository_owner }} password: ${{ secrets.GITHUB_TOKEN }} + + - name: Normalize image reference + id: image + env: + IMAGE: ghcr.io/${{ github.repository }}/${{ inputs.image_name }} + run: echo "name=${IMAGE,,}" >> "${GITHUB_OUTPUT}" - name: Prepare tags id: prepare-tags @@ -85,8 +115,8 @@ jobs: TAGS="type=raw,value=${{ inputs.tag_suffix }}-latest\n${TAGS}" fi - # Set Python 3.10 as the default image - if [[ "${{ inputs.include_latest_tags }}" == "true" && "${{ inputs.python_version }}" == "3.10" ]]; then + # Tag the designated default variant as the global `latest` image + if [[ "${{ inputs.include_latest_tags }}" == "true" && "${{ inputs.mark_as_latest }}" == "true" ]]; then TAGS="${TAGS}\ntype=raw,value=latest" fi @@ -100,23 +130,71 @@ jobs: id: meta uses: docker/metadata-action@v5 with: - images: ghcr.io/${{ github.repository }}/${{ inputs.image_name }} + images: ${{ steps.image.outputs.name }} tags: ${{ steps.prepare-tags.outputs.tags }} - name: Build and push Docker image + if: ${{ !inputs.push_by_digest }} id: build-push uses: docker/build-push-action@v6 with: context: . file: ${{ inputs.dockerfile_path }} + platforms: linux/${{ inputs.architecture }} push: true tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} build-args: ${{ inputs.build_args }} - cache-from: type=gha - cache-to: type=gha,mode=max + cache-from: type=gha,scope=${{ inputs.image_name }}-${{ inputs.tag_suffix }}-${{ inputs.architecture }} + cache-to: type=gha,mode=max,scope=${{ inputs.image_name }}-${{ inputs.tag_suffix }}-${{ inputs.architecture }} + + # Multi-architecture callers publish immutable manifests by digest here, + # then create the shared user-facing tags in a single downstream job. + # This prevents architecture jobs from racing to replace the same tag. + - name: Build and push Docker image by digest + if: ${{ inputs.push_by_digest }} + id: build-push-digest + uses: docker/build-push-action@v6 + with: + context: . + file: ${{ inputs.dockerfile_path }} + platforms: linux/${{ inputs.architecture }} + labels: ${{ steps.meta.outputs.labels }} + build-args: ${{ inputs.build_args }} + outputs: type=image,name=${{ steps.image.outputs.name }},push-by-digest=true,name-canonical=true,push=true + cache-from: type=gha,scope=${{ inputs.image_name }}-${{ inputs.tag_suffix }}-${{ inputs.architecture }} + cache-to: type=gha,mode=max,scope=${{ inputs.image_name }}-${{ inputs.tag_suffix }}-${{ inputs.architecture }} + + - name: Export digest + if: ${{ inputs.push_by_digest }} + env: + DIGEST: ${{ steps.build-push-digest.outputs.digest }} + run: | + if [[ -z "${{ inputs.digest_artifact_name }}" ]]; then + echo "digest_artifact_name is required when push_by_digest is true" >&2 + exit 1 + fi + mkdir -p /tmp/digests + touch "/tmp/digests/${DIGEST#sha256:}" + + - name: Upload digest + if: ${{ inputs.push_by_digest }} + uses: actions/upload-artifact@v4 + with: + name: ${{ inputs.digest_artifact_name }} + path: /tmp/digests/* + if-no-files-found: error + retention-days: 1 - name: Success message + if: ${{ !inputs.push_by_digest }} run: | - echo "✅ Python ${{ inputs.python_version }} image successfully built and pushed to ghcr.io/${{ github.repository }}/${{ inputs.image_name }}:${{ inputs.tag_suffix }}-sha-${GITHUB_SHA::7}" + echo "✅ Python ${{ inputs.python_version }} image successfully built and pushed to ${{ steps.image.outputs.name }}:${{ inputs.tag_suffix }}-sha-${GITHUB_SHA::7}" echo "To run tests with this image, manually trigger the 'Run Tests' workflow." + + - name: Digest success message + if: ${{ inputs.push_by_digest }} + env: + DIGEST: ${{ steps.build-push-digest.outputs.digest }} + run: | + echo "✅ Python ${{ inputs.python_version }} linux/${{ inputs.architecture }} image pushed as ${DIGEST}" diff --git a/.github/workflows/infra-build-image.yml b/.github/workflows/infra-build-image.yml index 6ed3f42e9..24c83a9d0 100644 --- a/.github/workflows/infra-build-image.yml +++ b/.github/workflows/infra-build-image.yml @@ -3,28 +3,13 @@ name: Build and Push Docker Images on: workflow_dispatch: inputs: - python_3_10: - description: 'Build Python 3.10 image' + build_cuda_matrix: + description: 'Build multi-arch CUDA images (12.6.3 default; 13.0.0 explicitly tagged)' required: false - default: false + default: true type: boolean - python_3_11: - description: 'Build Python 3.11 image' - required: false - default: false - type: boolean - python_3_12: - description: 'Build Python 3.12 image' - required: false - default: false - type: boolean - python_3_12_cuda_12_9: - description: 'Build Python 3.12 image Cuda 12.9' - required: false - default: false - type: boolean - dreamverse_cuda_12_9: - description: 'Build Dreamverse CUDA 12.9 backend-only and UI images' + build_dreamverse_matrix: + description: 'Build the amd64 Dreamverse matrix (backend + UI x CUDA 12.6.3/13.0.0)' required: false default: false type: boolean @@ -35,62 +20,179 @@ permissions: packages: write jobs: - build-python-3-10: - if: ${{ github.event.inputs.python_3_10 == 'true' }} - uses: ./.github/workflows/_template-build-image.yml - with: - python_version: '3.10' - dockerfile_path: docker/Dockerfile.python3.10 - tag_suffix: py3.10 - secrets: inherit - - build-python-3-11: - if: ${{ github.event.inputs.python_3_11 == 'true' }} - uses: ./.github/workflows/_template-build-image.yml - with: - python_version: '3.11' - dockerfile_path: docker/Dockerfile.python3.11 - tag_suffix: py3.11 - secrets: inherit - - build-python-3-12: - if: ${{ github.event.inputs.python_3_12 == 'true' }} + # CUDA matrix: Python 3.12 x {12.6.3, 13.0.0} x {amd64, arm64}. Each architecture + # builds natively and pushes only by digest; publish-cuda-manifests is the sole + # owner of the shared tags. CUDA 12.6 arm64 targets Hopper (sm_90a / GH200 class), + # because CUDA 12.6 cannot compile sm_121. CUDA 13 arm64 targets GB10 (sm_121). + # 12.6.3/cu126 owns the default `py3.12`/`latest` tags and keeps its versioned + # aliases; 13.0.0/cu130 is published under explicit versioned tags. Flash-attn + # 2.8.3 comes from the architecture-specific prebuilt releases. + build-cuda-images: + if: ${{ github.event.inputs.build_cuda_matrix == 'true' }} + strategy: + fail-fast: false + matrix: + cuda: + - version: '12.6.3' + suffix: '-cuda12.6.3' + torch_backend: 'cu126' + fa_tag: 'cu126torch2.12' + cmake_build_parallel_level: '4' + torch_cuda_arch_list: + amd64: '9.0a' + arm64: '9.0a' + - version: '13.0.0' + suffix: '-cuda13.0.0' + torch_backend: 'cu130' + fa_tag: 'cu130torch2.12' + cmake_build_parallel_level: '1' + torch_cuda_arch_list: + amd64: '9.0a' + arm64: '12.1' + architecture: + - name: amd64 + runner: ubuntu-latest + - name: arm64 + runner: ubuntu-24.04-arm uses: ./.github/workflows/_template-build-image.yml with: python_version: '3.12' - dockerfile_path: docker/Dockerfile.python3.12 - tag_suffix: py3.12 + dockerfile_path: docker/Dockerfile + tag_suffix: py3.12${{ matrix.cuda.suffix }} + runner: ${{ matrix.architecture.runner }} + architecture: ${{ matrix.architecture.name }} + push_by_digest: true + digest_artifact_name: fastvideo-dev-cuda${{ matrix.cuda.version }}-${{ matrix.architecture.name }} + build_args: | + PYTHON_VERSION=3.12 + CUDA_VERSION=${{ matrix.cuda.version }} + UV_TORCH_BACKEND=${{ matrix.cuda.torch_backend }} + TORCH_CUDA_ARCH_LIST=${{ matrix.cuda.torch_cuda_arch_list[matrix.architecture.name] }} + CMAKE_BUILD_PARALLEL_LEVEL=${{ matrix.cuda.cmake_build_parallel_level }} + FLASH_ATTN_WHEEL_TAG=${{ matrix.cuda.fa_tag }} + FLASH_ATTN_WHEEL_RELEASE=https://github.com/mjun0812/flash-attention-prebuild-wheels/releases/download/v0.9.17 + FLASH_ATTN_WHEEL_RELEASE_ARM64=https://github.com/mjun0812/flash-attention-prebuild-wheels/releases/download/v0.9.22 secrets: inherit - build-python-3-12-cuda-12-9: - if: ${{ github.event.inputs.python_3_12_cuda_12_9 == 'true' }} - uses: ./.github/workflows/_template-build-image.yml - with: - python_version: '3.12' - dockerfile_path: docker/Dockerfile.python3.12.cuda12.9.1 - tag_suffix: py3.12-cuda12.9.1 - secrets: inherit + publish-cuda-manifests: + # !cancelled(): a failed sibling build leg must not skip the manifests for a + # CUDA lane whose own digests all exist; the digest-count check below fails + # the incomplete lane loudly instead. + if: ${{ !cancelled() && github.event.inputs.build_cuda_matrix == 'true' }} + needs: build-cuda-images + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + strategy: + fail-fast: false + matrix: + cuda: + - version: '12.6.3' + suffix: '-cuda12.6.3' + is_default: true + - version: '13.0.0' + suffix: '-cuda13.0.0' + is_default: false + steps: + - name: Download architecture digests + uses: actions/download-artifact@v4 + with: + path: /tmp/digests + pattern: fastvideo-dev-cuda${{ matrix.cuda.version }}-* + merge-multiple: true - build-dreamverse-backend-cuda-12-9: - if: ${{ github.event.inputs.dreamverse_cuda_12_9 == 'true' }} + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Login to GitHub Container Registry + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.repository_owner }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Normalize image reference + id: image + env: + IMAGE: ghcr.io/${{ github.repository }}/fastvideo-dev + run: echo "name=${IMAGE,,}" >> "${GITHUB_OUTPUT}" + + - name: Prepare tags + id: prepare-tags + env: + VERSIONED_TAG_PREFIX: py3.12${{ matrix.cuda.suffix }} + PUBLISH_DEFAULT_TAGS: ${{ matrix.cuda.is_default }} + run: | + SHORT_SHA="${GITHUB_SHA::7}" + TAGS="type=raw,value=${VERSIONED_TAG_PREFIX}-latest\ntype=raw,value=${VERSIONED_TAG_PREFIX}-sha-${SHORT_SHA}" + if [[ "${PUBLISH_DEFAULT_TAGS}" == "true" ]]; then + TAGS="type=raw,value=latest\ntype=raw,value=py3.12-latest\ntype=raw,value=py3.12-sha-${SHORT_SHA}\n${TAGS}" + fi + { + echo "tags<> "${GITHUB_OUTPUT}" + + - name: Extract metadata for Docker + id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ steps.image.outputs.name }} + tags: ${{ steps.prepare-tags.outputs.tags }} + + - name: Create multi-architecture manifests + env: + IMAGE: ${{ steps.image.outputs.name }} + run: | + mapfile -t DIGESTS < <(find /tmp/digests -maxdepth 1 -type f -printf '%f\n' | sort) + if [[ "${#DIGESTS[@]}" -ne 2 ]]; then + echo "Expected exactly two architecture digests, found ${#DIGESTS[@]}" >&2 + exit 1 + fi + + mapfile -t TAGS < <(jq -r '.tags[]' <<< "${DOCKER_METADATA_OUTPUT_JSON}") + TAG_ARGS=() + for TAG in "${TAGS[@]}"; do + TAG_ARGS+=(--tag "${TAG}") + done + + IMAGE_REFS=() + for DIGEST in "${DIGESTS[@]}"; do + IMAGE_REFS+=("${IMAGE}@sha256:${DIGEST}") + done + + docker buildx imagetools create "${TAG_ARGS[@]}" "${IMAGE_REFS[@]}" + docker buildx imagetools inspect "${TAGS[0]}" + + # Dreamverse matrix: {backend, UI} x {12.6.3, 13.0.0}, Python 3.12. Torch backend + # matches the base CUDA (cu126 / cu130). Keep these images amd64-only until the + # required FA4 dependency stack is available and validated on arm64. + build-dreamverse: + if: ${{ github.event.inputs.build_dreamverse_matrix == 'true' }} + strategy: + fail-fast: false + matrix: + cuda: + - version: '12.6.3' + torch_backend: 'cu126' + - version: '13.0.0' + torch_backend: 'cu130' + variant: + - name: backend + ui: '0' + - name: ui + ui: '1' uses: ./.github/workflows/_template-build-image.yml with: python_version: '3.12' dockerfile_path: apps/dreamverse/docker/Dockerfile - tag_suffix: dreamverse-backend-cuda12.9.1 + tag_suffix: dreamverse-${{ matrix.variant.name }}-cuda${{ matrix.cuda.version }} image_name: dreamverse - build_args: BUILD_DREAMVERSE_UI=0 - include_latest_tags: false - secrets: inherit - - build-dreamverse-ui-cuda-12-9: - if: ${{ github.event.inputs.dreamverse_cuda_12_9 == 'true' }} - uses: ./.github/workflows/_template-build-image.yml - with: - python_version: '3.12' - dockerfile_path: apps/dreamverse/docker/Dockerfile - tag_suffix: dreamverse-ui-cuda12.9.1 - image_name: dreamverse - build_args: BUILD_DREAMVERSE_UI=1 + build_args: | + CUDA_VERSION=${{ matrix.cuda.version }} + UV_TORCH_BACKEND=${{ matrix.cuda.torch_backend }} + BUILD_DREAMVERSE_UI=${{ matrix.variant.ui }} include_latest_tags: false secrets: inherit diff --git a/AGENTS.md b/AGENTS.md index 06126c49f..ff18d09d5 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -11,7 +11,7 @@ - Static assets: `assets/` (including `assets/images/`, `assets/videos/`, and `assets/prompts/`) and `comfyui/assets/`. ## Build, Test, and Development Commands -- `uv pip install -e ".[dev]"`: editable install with lint/test extras. +- `UV_TORCH_BACKEND=cu126 uv pip install -e ".[dev]"`: editable CUDA 12 install with lint/test extras (`cu130` on CUDA 13). - `pre-commit install --hook-type pre-commit --hook-type commit-msg`: enable local hooks. - `pre-commit run --all-files`: run formatter/lint/type/spelling checks. - `pytest tests/`: run top-level test suite. diff --git a/README.md b/README.md index 966e97913..1f6c1d6d2 100644 --- a/README.md +++ b/README.md @@ -55,12 +55,37 @@ We recommend using [uv](https://docs.astral.sh/uv/) to create a clean environmen uv venv --python 3.12 --seed source .venv/bin/activate -# Install FastVideo -uv pip install fastvideo +# Install FastVideo on NVIDIA CUDA 12 +UV_TORCH_BACKEND=cu126 uv pip install fastvideo ``` +Use `UV_TORCH_BACKEND=cu130` on CUDA 13. Apple silicon users should follow the +[MPS installation guide](https://hao-ai-lab.github.io/FastVideo/getting_started/installation/mps/). + Please see our [docs](https://hao-ai-lab.github.io/FastVideo/getting_started/installation/) for more detailed installation instructions. +> **On an NVIDIA DGX Spark (GB10 / ARM64 + CUDA 13)?** There's no prebuilt ARM wheel for the FastVideo CUDA kernel, so it's an editable from-source install (`UV_TORCH_BACKEND=cu130 uv pip install -e .`, which compiles that kernel for you) rather than `UV_TORCH_BACKEND=cu130 uv pip install fastvideo`. A compatible prebuilt ARM64 FlashAttention wheel is available separately. Follow the [DGX Spark install guide](https://hao-ai-lab.github.io/FastVideo/getting_started/installation/spark/). + +### Install with an AI coding agent + +FastVideo is a monorepo with rich agent guidance (see [`AGENTS.md`](AGENTS.md)). If you use Claude Code, Cursor, or another coding agent, paste the prompt below — it detects your platform and follows the matching guide: + +```text +Install FastVideo (https://github.com/hao-ai-lab/FastVideo) into a fresh uv virtual environment. + +1. Detect the platform: run `uname -m`, `nvidia-smi`, and `nvcc --version`. +2. Read and follow the matching install guide exactly (in this repo, or at + https://hao-ai-lab.github.io/FastVideo/getting_started/installation/): + - NVIDIA GPU, x86_64 -> docs/getting_started/installation/gpu.md + - NVIDIA DGX Spark / GB10, aarch64, CUDA 13 -> docs/getting_started/installation/spark.md + - Apple Silicon, macOS -> docs/getting_started/installation/mps.md +3. Use uv for every step. If a command fails, debug it and tell me what you changed. +4. Verify the result: + python -c "import fastvideo, torch; print('cuda', torch.cuda.is_available())" + fastvideo --help +5. Report which platform you detected and any deviations you had to make. +``` + ## Sparse Distillation For our sparse distillation techniques, please see our [distillation docs](https://hao-ai-lab.github.io/FastVideo/distillation/dmd/) and check out our [blog](https://hao-ai-lab.github.io/blogs/fastvideo_post_training/). diff --git a/apps/dreamverse/README.md b/apps/dreamverse/README.md index 6415d3eaf..783fa58e5 100644 --- a/apps/dreamverse/README.md +++ b/apps/dreamverse/README.md @@ -15,7 +15,7 @@ pip install --upgrade pip pip install uv uv venv .venv --python 3.12 source .venv/bin/activate -uv pip install "fastvideo[dreamverse]" +UV_TORCH_BACKEND=cu126 uv pip install "fastvideo[dreamverse]" ``` ### Method 2: From source @@ -28,9 +28,11 @@ pip install --upgrade pip pip install uv uv venv .venv --python 3.12 source .venv/bin/activate -uv pip install -e ".[dreamverse]" +UV_TORCH_BACKEND=cu126 uv pip install -e ".[dreamverse]" ``` +Use `UV_TORCH_BACKEND=cu130` instead on CUDA 13. + ### Method 3: Using Docker ```bash @@ -244,8 +246,9 @@ npm run e2e `dreamverse-server` exits with an install hint -- install the Dreamverse extra with `uv pip install -e ".[dreamverse]"` from a - source checkout, or `uv pip install "fastvideo[dreamverse]"` from PyPI. +- install the Dreamverse extra with `UV_TORCH_BACKEND=cu126 uv pip install -e ".[dreamverse]"` from a + source checkout, or `UV_TORCH_BACKEND=cu126 uv pip install "fastvideo[dreamverse]"` from PyPI + (`cu130` on CUDA 13). Prompt-provider environment variable errors diff --git a/apps/dreamverse/docker/Dockerfile b/apps/dreamverse/docker/Dockerfile index cf039dd84..fad028d6a 100644 --- a/apps/dreamverse/docker/Dockerfile +++ b/apps/dreamverse/docker/Dockerfile @@ -1,13 +1,23 @@ # syntax=docker/dockerfile:1.7 -ARG CUDA_TAG=12.9.1-cudnn-devel-ubuntu22.04 -FROM nvidia/cuda:${CUDA_TAG} +# CUDA base. CUDA_VERSION/UBUNTU_VERSION feed the default tag (matches the +# unified docker/Dockerfile); override BUILD_BASE_IMAGE wholesale for a mirror. +ARG CUDA_VERSION=13.0.0 +ARG UBUNTU_VERSION=22.04 +ARG BUILD_BASE_IMAGE=nvidia/cuda:${CUDA_VERSION}-cudnn-devel-ubuntu${UBUNTU_VERSION} +FROM ${BUILD_BASE_IMAGE} ARG BUILD_FASTVIDEO_KERNEL_FROM_SOURCE=0 ARG BUILD_DREAMVERSE_UI=0 ENV DEBIAN_FRONTEND=noninteractive \ PYTHONUNBUFFERED=1 \ - UV_LINK_MODE=copy + UV_LINK_MODE=copy \ + UV_CACHE_DIR=/opt/uv/cache + +# pyproject no longer pins a PyTorch index; GPU-less build host -> pin explicitly +# (auto would fall back to CPU). Matches the base CUDA: cu130 (13.0) / cu126 (12.6). +ARG UV_TORCH_BACKEND=cu130 +ENV UV_TORCH_BACKEND=${UV_TORCH_BACKEND} SHELL ["/bin/bash", "-c"] @@ -20,7 +30,8 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ && update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-11 100 \ --slave /usr/bin/g++ g++ /usr/bin/g++-11 -ENV CUDA_HOME=/usr/local/cuda-12.9 +# Version-agnostic /usr/local/cuda symlink so this works for any CUDA_VERSION. +ENV CUDA_HOME=/usr/local/cuda ENV PATH=/root/.local/bin:/opt/venv/bin:${CUDA_HOME}/bin:${PATH} ENV LD_LIBRARY_PATH=${CUDA_HOME}/lib64:${LD_LIBRARY_PATH} ENV VIRTUAL_ENV=/opt/venv @@ -34,17 +45,17 @@ WORKDIR /opt/FastVideo COPY . /opt/FastVideo -RUN source /opt/venv/bin/activate \ - && uv pip install --no-cache-dir "/opt/FastVideo[dreamverse]" +RUN --mount=type=cache,target=/opt/uv/cache \ + source /opt/venv/bin/activate \ + && uv pip install "/opt/FastVideo[dreamverse]" # Standard docker build does not expose GPUs, while fastvideo-kernel/build.sh # detects the CUDA architecture with torch at build time. The FastVideo package # install above brings in the pinned fastvideo-kernel package; rebuild from the # copied source only on hosts configured for build-time GPU access. -RUN if [[ "${BUILD_FASTVIDEO_KERNEL_FROM_SOURCE}" == "1" ]]; then \ - sed -i 's/^git submodule update --init --recursive$/if [[ -d ..\/.git ]]; then git submodule update --init --recursive; fi/' \ - /opt/FastVideo/fastvideo-kernel/build.sh \ - && source /opt/venv/bin/activate \ +RUN --mount=type=cache,target=/opt/uv/cache \ + if [[ "${BUILD_FASTVIDEO_KERNEL_FROM_SOURCE}" == "1" ]]; then \ + source /opt/venv/bin/activate \ && cd /opt/FastVideo/fastvideo-kernel \ && ./build.sh; \ else \ diff --git a/apps/dreamverse/docker/README.md b/apps/dreamverse/docker/README.md index 8ad718077..4177463a8 100644 --- a/apps/dreamverse/docker/README.md +++ b/apps/dreamverse/docker/README.md @@ -27,10 +27,26 @@ BUILD_DREAMVERSE_UI=1 DREAMVERSE_IMAGE= apps/dreamverse/docker/docker Prefer SHA-specific tags for deployable images; avoid `latest`. -The Dockerfile builds a CUDA 12.9.1 image, installs FastVideo from this -checkout with the `dreamverse` extra, installs the FA4 -flash-attention fork, builds native FFmpeg, and installs FlashInfer for NVFP4 -quantization. +The Dockerfile defaults to CUDA 13.0.0 with the cu130 PyTorch backend. CI also +builds a CUDA 12.6.3 / cu126 image. Select that local build explicitly with: + +```bash +CUDA_VERSION=12.6.3 apps/dreamverse/docker/docker_build.sh +``` + +The helper derives `UV_TORCH_BACKEND=cu126` for CUDA 12.x and `cu130` for CUDA +13.x. Set `UV_TORCH_BACKEND` explicitly for a custom CUDA version. The legacy +complete-image-tag override remains supported and selects the same matching +backend: + +```bash +CUDA_TAG=12.6.3-cudnn-devel-ubuntu22.04 apps/dreamverse/docker/docker_build.sh +``` + +Do not set `CUDA_TAG` and `CUDA_VERSION` together. The image installs FastVideo +from this checkout with the `dreamverse` extra, including the FA4 +flash-attention fork and FlashInfer for NVFP4 quantization, and builds native +FFmpeg. FastVideo's pinned `fastvideo-kernel==0.3.0` package is installed by default. To rebuild `fastvideo-kernel` from this checkout during the image build, set: diff --git a/apps/dreamverse/docker/docker_build.sh b/apps/dreamverse/docker/docker_build.sh index 7eb742de7..8b631c555 100755 --- a/apps/dreamverse/docker/docker_build.sh +++ b/apps/dreamverse/docker/docker_build.sh @@ -21,7 +21,47 @@ if [[ -z "${DOCKER_BUILDKIT:-}" ]] && docker buildx version >/dev/null 2>&1; the fi build_args=() -[[ -n "${CUDA_TAG:-}" ]] && build_args+=(--build-arg "CUDA_TAG=${CUDA_TAG}") +cuda_version="${CUDA_VERSION:-}" +torch_backend="${UV_TORCH_BACKEND:-}" + +# CUDA_TAG was the Dockerfile's original override and contains the complete +# nvidia/cuda tag (for example, 12.6.3-cudnn-devel-ubuntu22.04). Keep accepting +# it while translating it to the parameterized Dockerfile inputs. +if [[ -n "${CUDA_TAG:-}" ]]; then + if [[ -n "${CUDA_VERSION:-}" ]]; then + printf 'CUDA_TAG and CUDA_VERSION cannot both be set. Use CUDA_VERSION for new builds.\n' >&2 + exit 2 + fi + + cuda_version="${CUDA_TAG%%-*}" + if [[ ! "${cuda_version}" =~ ^[0-9]+\.[0-9]+(\.[0-9]+)?$ ]]; then + printf 'Cannot infer CUDA_VERSION from CUDA_TAG=%s. Use CUDA_VERSION and UV_TORCH_BACKEND instead.\n' \ + "${CUDA_TAG}" >&2 + exit 2 + fi + build_args+=(--build-arg "BUILD_BASE_IMAGE=nvidia/cuda:${CUDA_TAG}") +fi + +if [[ -n "${cuda_version}" ]]; then + build_args+=(--build-arg "CUDA_VERSION=${cuda_version}") +fi + +if [[ -z "${torch_backend}" && -n "${cuda_version}" ]]; then + case "${cuda_version}" in + 12.*) torch_backend=cu126 ;; + 13.*) torch_backend=cu130 ;; + *) + printf 'No default UV_TORCH_BACKEND for CUDA_VERSION=%s. Set UV_TORCH_BACKEND explicitly.\n' \ + "${cuda_version}" >&2 + exit 2 + ;; + esac +fi + +if [[ -n "${torch_backend}" ]]; then + build_args+=(--build-arg "UV_TORCH_BACKEND=${torch_backend}") +fi + [[ -n "${BUILD_FASTVIDEO_KERNEL_FROM_SOURCE:-}" ]] && \ build_args+=(--build-arg "BUILD_FASTVIDEO_KERNEL_FROM_SOURCE=${BUILD_FASTVIDEO_KERNEL_FROM_SOURCE}") build_args+=(--build-arg "BUILD_DREAMVERSE_UI=${BUILD_DREAMVERSE_UI:-0}") diff --git a/apps/dreamverse/pyproject.toml b/apps/dreamverse/pyproject.toml index cc44a3bcf..39353e5cb 100644 --- a/apps/dreamverse/pyproject.toml +++ b/apps/dreamverse/pyproject.toml @@ -14,7 +14,7 @@ dependencies = [ [project.optional-dependencies] server = [ "cerebras-cloud-sdk", - "flash-attn-cute @ git+https://github.com/XOR-op/flash-attention.git@fa4-compile#subdirectory=flash_attn/cute", + "flash-attn-4 @ git+https://github.com/Dao-AILab/flash-attention.git@940cd9680f3315f2f06b43ab5bea2c2cf2d96806#subdirectory=flash_attn/cute", "flashinfer-python", "openai>=1.40", ] diff --git a/apps/dreamverse/scripts/modal/README.md b/apps/dreamverse/scripts/modal/README.md index 6e8ea1738..154846993 100644 --- a/apps/dreamverse/scripts/modal/README.md +++ b/apps/dreamverse/scripts/modal/README.md @@ -36,9 +36,17 @@ DREAMVERSE_IMAGE=ghcr.io///dreamverse: \ modal deploy apps/dreamverse/scripts/modal/modal_app.py ``` -Use a SHA-specific tag, not `latest`. Use a `dreamverse-backend-cuda12.9.1-sha-*` -tag for backend-only deploys, or a `dreamverse-ui-cuda12.9.1-sha-*` tag for an -image that includes the static UI served by the backend. +Use a SHA-specific tag, not `latest`. The image workflow publishes backend and +UI variants for CUDA 12.6.3 and CUDA 13.0.0: + +- `dreamverse-backend-cuda13.0.0-sha-*` or + `dreamverse-ui-cuda13.0.0-sha-*` for the CUDA 13 / cu130 images +- `dreamverse-backend-cuda12.6.3-sha-*` or + `dreamverse-ui-cuda12.6.3-sha-*` for the CUDA 12 / cu126 images + +The Dockerfile defaults to the CUDA 13 / cu130 lane, which is the recommended +image for this B200 deployment. Choose the `ui` variant only when the backend +should serve the static UI. For local image build details, see `apps/dreamverse/docker/README.md` and `apps/dreamverse/docker/docker_build.sh`. diff --git a/apps/dreamverse/scripts/modal/modal_app.py b/apps/dreamverse/scripts/modal/modal_app.py index 017079b41..b0919a74a 100644 --- a/apps/dreamverse/scripts/modal/modal_app.py +++ b/apps/dreamverse/scripts/modal/modal_app.py @@ -10,8 +10,9 @@ IMAGE = os.environ.get("DREAMVERSE_IMAGE") if not IMAGE: raise RuntimeError( "DREAMVERSE_IMAGE is required. Set it to a published SHA-specific Dreamverse image, " - "for example a dreamverse-backend-cuda12.9.1-sha-* tag or a " - "dreamverse-ui-cuda12.9.1-sha-* tag if serving the static UI." + "for example a dreamverse-backend-cuda13.0.0-sha-* tag or a " + "dreamverse-ui-cuda13.0.0-sha-* tag if serving the static UI. " + "CUDA 12 / cu126 images use the corresponding cuda12.6.3 tag." ) # ``@modal.web_server`` invokes ``serve()`` directly and bypasses the image diff --git a/comfyui/README.md b/comfyui/README.md index 8767ae8a2..e178e4916 100644 --- a/comfyui/README.md +++ b/comfyui/README.md @@ -41,9 +41,11 @@ cp -r /path/to/FastVideo/comfyui /path/to/ComfyUI/custom_nodes/FastVideo Currently, the only dependency is `fastvideo`, which can be installed with `uv`. ```bash -uv pip install fastvideo +UV_TORCH_BACKEND=cu126 uv pip install fastvideo ``` +Use `UV_TORCH_BACKEND=cu130` instead on CUDA 13. + #### Install missing custom nodes: `ComfyUI-VideoHelperSuite`: diff --git a/docker/Dockerfile b/docker/Dockerfile new file mode 100644 index 000000000..c229a9eaf --- /dev/null +++ b/docker/Dockerfile @@ -0,0 +1,208 @@ +# syntax=docker/dockerfile:1.7 +# ============================================================================= +# FastVideo CUDA development image (unified, parameterized) +# ============================================================================= +# Replaces the former per-Python Dockerfiles +# (Dockerfile.python3.10 / 3.11 / 3.12 / 3.12.cuda12.9.1): pick a variant with +# --build-arg instead of maintaining a separate near-identical file. +# +# # Default image (Python 3.12, CUDA 13.0.0, Ubuntu 22.04, Hopper sm_90a): +# docker build -f docker/Dockerfile -t fastvideo-dev:py3.12 . +# +# # Python 3.10 / 3.11: +# docker build -f docker/Dockerfile --build-arg PYTHON_VERSION=3.11 \ +# -t fastvideo-dev:py3.11 . +# +# # CUDA 13.0.0: match the torch backend + flash-attn wheel to the base CUDA. +# docker build -f docker/Dockerfile \ +# --build-arg CUDA_VERSION=13.0.0 \ +# --build-arg UV_TORCH_BACKEND=cu130 \ +# --build-arg FLASH_ATTN_WHEEL_TAG=cu130torch2.12 \ +# -t fastvideo-dev:py3.12-cuda13.0.0 . +# +# # DGX Spark / GB10 (aarch64, Blackwell sm_121): the matching arm64 +# # prebuilt flash-attn wheel is selected automatically from TARGETARCH. +# docker build --platform linux/arm64 -f docker/Dockerfile \ +# --build-arg TORCH_CUDA_ARCH_LIST=12.1 -t fastvideo-dev:spark . +# +# The ARG defaults below are the source of truth for the default image. +# ROCm builds use docker/rocm7_1.Dockerfile (different base + flow). +# ============================================================================= + +# --- Base image selection (overridable for private mirrors / new CUDA) ------- +# Build on the -devel- image: nvcc + the full toolkit are needed to compile +# fastvideo-kernel. CUDA_VERSION/UBUNTU_VERSION feed the default tag, or override +# BUILD_BASE_IMAGE wholesale for a mirror. +ARG CUDA_VERSION=13.0.0 +ARG UBUNTU_VERSION=22.04 +ARG BUILD_BASE_IMAGE=nvidia/cuda:${CUDA_VERSION}-cudnn-devel-ubuntu${UBUNTU_VERSION} + +FROM ${BUILD_BASE_IMAGE} AS base + +# Redeclare the ARGs we need inside the stage (FROM consumes the global ones). +ARG CUDA_VERSION +ARG PYTHON_VERSION=3.12 + +# CUDA arch fastvideo-kernel is compiled for. Build machines have no GPU, so +# build.sh uses this instead of probing a live device. Default Hopper (sm_90a), +# matching the released kernel wheel; override e.g. TORCH_CUDA_ARCH_LIST=12.1 +# for GB10/Blackwell (DGX Spark). +ARG TORCH_CUDA_ARCH_LIST=9.0a + +# Cap CMake/Ninja concurrency to fit standard 16 GB build runners. This default +# matches the CUDA 13 kernel-release build; the CUDA 12.6 image workflow uses 4. +ARG CMAKE_BUILD_PARALLEL_LEVEL=1 + +# flash-attn: when FLASH_ATTN_WHEEL_TAG is set, install the matching prebuilt wheel +# for TARGETARCH. The prebuilt-wheel project publishes x86_64 and arm64 wheels in +# different releases, so each architecture has its own release URL. Set the wheel +# tag to empty only when an explicit source build is desired. The wheel's +# cuXXXtorchY.Y tag must match UV_TORCH_BACKEND (below): cu130torch2.12 for the +# default 13.0 base, cu126torch2.12 for 12.6 (both backends are on torch 2.12). +ARG FLASH_ATTN_VERSION=2.8.3 +ARG FLASH_ATTN_WHEEL_TAG=cu130torch2.12 +ARG FLASH_ATTN_WHEEL_RELEASE=https://github.com/mjun0812/flash-attention-prebuild-wheels/releases/download/v0.9.17 +ARG FLASH_ATTN_WHEEL_RELEASE_ARM64=https://github.com/mjun0812/flash-attention-prebuild-wheels/releases/download/v0.9.22 + +# The prebuilt flash-attn wheel ships an FA4 `flash_attn.cute` built against the +# cutlass-4.4 `cute.core.ThrMma` API, which crashes on the cutlass-dsl 4.5 that +# flashinfer/quack pull in. After the wheel install we overlay this cutlass-4.5-safe +# upstream cute (flash-attn-4) so the image runs FA4 instead of the FA2 fallback. +ARG FA4_CUTE_REF=940cd9680f3315f2f06b43ab5bea2c2cf2d96806 + +# Provided automatically by BuildKit/buildx (e.g. "amd64" / "arm64") and used to +# select the prebuilt flash-attn wheel. Empty under a plain `docker build` without +# BuildKit, which we treat as x86_64 for compatibility with the historical build. +ARG TARGETARCH + +ENV DEBIAN_FRONTEND=noninteractive + +SHELL ["/bin/bash", "-c"] + +WORKDIR /FastVideo + +RUN apt-get update && apt-get install -y --no-install-recommends \ + wget \ + git \ + ca-certificates \ + openssh-server \ + zsh \ + vim \ + curl \ + gcc-11 \ + g++-11 \ + clang-11 \ + && rm -rf /var/lib/apt/lists/* + +# Set up C++20 compilers for ThunderKittens +RUN update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-11 100 --slave /usr/bin/g++ g++ /usr/bin/g++-11 + +# Set CUDA environment variables. Use the version-agnostic /usr/local/cuda +# symlink the nvidia images provide so this works for any CUDA_VERSION. +ENV CUDA_HOME=/usr/local/cuda +ENV PATH=${CUDA_HOME}/bin:${PATH} +ENV LD_LIBRARY_PATH=${CUDA_HOME}/lib64:$LD_LIBRARY_PATH + +# The uv package cache lives in a BuildKit cache mount (see RUNs below), not in an +# image layer, so we can drop the old --no-cache-dir to speed up rebuilds without +# bloating the image. Copy link mode avoids hardlink errors across the mount. +ENV UV_CACHE_DIR=/opt/uv/cache +ENV UV_LINK_MODE=copy + +# pyproject no longer pins a PyTorch index; select the build at resolve time. +# Build hosts have no GPU driver (auto-detect would fall back to CPU), so pin +# explicitly, matching the base CUDA: cu130 (default, 13.0) / cu126 (12.6), both +# on torch 2.12. Keep FLASH_ATTN_WHEEL_TAG in sync. +ARG UV_TORCH_BACKEND=cu130 +ENV UV_TORCH_BACKEND=${UV_TORCH_BACKEND} + +# Install uv and source its environment +RUN curl -LsSf https://astral.sh/uv/install.sh | sh && \ + echo 'source $HOME/.local/bin/env' >> /root/.bashrc + +# Copy only dependency metadata first to leverage Docker cache. The kernel is +# deliberately excluded from this layer: arm64 resolves it to the in-tree source, +# which needs the complete checkout and CUDA architecture settings below. +COPY pyproject.toml ./ +COPY docker/uv-excludes ./docker/uv-excludes + +# Create a dummy README to satisfy the installation +RUN echo "# Placeholder" > README.md + +# Create the venv and install the (slow-changing) dependency tree + flash-attn +# from pyproject.toml alone, before the source is copied in, so that editing +# source below does not bust this expensive layer. +RUN --mount=type=cache,target=/opt/uv/cache \ + source $HOME/.local/bin/env && \ + uv venv --python ${PYTHON_VERSION} --seed /opt/venv && \ + source /opt/venv/bin/activate && \ + uv pip install --upgrade pip && \ + uv pip install --excludes docker/uv-excludes ".[dev]" && \ + PYTAG=cp$(echo "${PYTHON_VERSION}" | tr -d .) && \ + case "${TARGETARCH:-amd64}" in \ + amd64) \ + FLASH_ATTN_WHEEL_PLATFORM=linux_x86_64; \ + FLASH_ATTN_TARGET_RELEASE=${FLASH_ATTN_WHEEL_RELEASE} \ + ;; \ + arm64) \ + FLASH_ATTN_WHEEL_PLATFORM=linux_aarch64; \ + FLASH_ATTN_TARGET_RELEASE=${FLASH_ATTN_WHEEL_RELEASE_ARM64} \ + ;; \ + *) \ + echo "Unsupported TARGETARCH for flash-attn: ${TARGETARCH}" >&2; \ + exit 1 \ + ;; \ + esac && \ + if [ -n "${FLASH_ATTN_WHEEL_TAG}" ]; then \ + echo "Installing prebuilt flash-attn wheel (${FLASH_ATTN_WHEEL_TAG}, ${PYTAG}, ${FLASH_ATTN_WHEEL_PLATFORM})" && \ + uv pip install "${FLASH_ATTN_TARGET_RELEASE}/flash_attn-${FLASH_ATTN_VERSION}+${FLASH_ATTN_WHEEL_TAG}-${PYTAG}-${PYTAG}-${FLASH_ATTN_WHEEL_PLATFORM}.whl"; \ + else \ + echo "Building flash-attn ${FLASH_ATTN_VERSION} from source" && \ + uv pip install flash-attn==${FLASH_ATTN_VERSION} --no-build-isolation; \ + fi + +# Overlay the cutlass-4.5-safe upstream FA4 cute (FA4_CUTE_REF) over the +# wheel/source one so the image runs FA4, not the FA2 fallback. This pulls the FA4 +# runtime stack (cutlass-dsl, quack-kernels, apache-tvm-ffi, torch-c-dlpack-ext) -- +# the same deps the [dreamverse] extra already installs in CI; the installed torch +# is left untouched. The cute package only ships flash_attn/cute (never +# flash_attn/__init__.py), so FA2/varlen/bert_padding stay from the install above; +# rmtree clears the wheel's stale cute files first to avoid an install conflict. +# Then verify both survive so a broken overlay fails the build instead of shipping +# an FA2-less image. x86 only: the FA4 stack (quack-kernels etc.) is unvalidated on +# arm64 / GB10 (sm_121), so there we skip the overlay and FA4 falls back to FA2. +RUN --mount=type=cache,target=/opt/uv/cache \ + source $HOME/.local/bin/env && \ + source /opt/venv/bin/activate && \ + if [ "${TARGETARCH}" = "arm64" ]; then \ + echo "Skipping FA4 cute overlay on arm64 (FA4 stack unvalidated there; FA4 falls back to FA2)"; \ + else \ + python -c "import glob, shutil; [shutil.rmtree(d, ignore_errors=True) for d in glob.glob('/opt/venv/lib/python*/site-packages/flash_attn/cute')]" && \ + uv pip install "flash-attn-4 @ git+https://github.com/Dao-AILab/flash-attention.git@${FA4_CUTE_REF}#subdirectory=flash_attn/cute" && \ + python -c "import flash_attn; assert hasattr(flash_attn, 'flash_attn_func'), 'FA2 was clobbered by the cute overlay'; import flash_attn.cute; print('FA2 + FA4 cute OK')"; \ + fi + +COPY . . + +# Install FastVideo Unified Kernel exactly once. build.sh initializes only its +# CUTLASS/ThunderKittens submodules, then compiles for TORCH_CUDA_ARCH_LIST +# (default Hopper sm_90a) without requiring a live GPU. +RUN --mount=type=cache,target=/opt/uv/cache \ + source $HOME/.local/bin/env && \ + source /opt/venv/bin/activate && \ + cd fastvideo-kernel && \ + CMAKE_BUILD_PARALLEL_LEVEL=${CMAKE_BUILD_PARALLEL_LEVEL} \ + TORCH_CUDA_ARCH_LIST=${TORCH_CUDA_ARCH_LIST} ./build.sh + +# Install FastVideo itself (editable) now that the source is present, and set up +# shell configuration. Dependencies and the local kernel are already installed, +# so --no-deps prevents uv from resolving/building the ARM source override again. +RUN --mount=type=cache,target=/opt/uv/cache \ + source $HOME/.local/bin/env && \ + source /opt/venv/bin/activate && \ + uv pip install -e ".[dev]" --no-deps && \ + (git config --unset-all http.https://github.com/.extraheader || true) && \ + echo 'source /opt/venv/bin/activate' >> /root/.bashrc && \ + echo 'if [ -n "$ZSH_VERSION" ] && [ -f ~/.zshrc ]; then . ~/.zshrc; elif [ -f ~/.bashrc ]; then . ~/.bashrc; fi' > /root/.profile + +EXPOSE 22 diff --git a/docker/Dockerfile.python3.10 b/docker/Dockerfile.python3.10 deleted file mode 100644 index ba5f1afb4..000000000 --- a/docker/Dockerfile.python3.10 +++ /dev/null @@ -1,68 +0,0 @@ -FROM nvidia/cuda:12.8.0-cudnn-devel-ubuntu22.04 - -ENV DEBIAN_FRONTEND=noninteractive - -SHELL ["/bin/bash", "-c"] - -WORKDIR /FastVideo - -RUN apt-get update && apt-get install -y --no-install-recommends \ - wget \ - git \ - ca-certificates \ - openssh-server \ - zsh \ - vim \ - curl \ - gcc-11 \ - g++-11 \ - clang-11 \ - && rm -rf /var/lib/apt/lists/* - -# Set up C++20 compilers for ThunderKittens -RUN update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-11 100 --slave /usr/bin/g++ g++ /usr/bin/g++-11 - -# Set CUDA environment variables -ENV CUDA_HOME=/usr/local/cuda-12.8 -ENV PATH=${CUDA_HOME}/bin:${PATH} -ENV LD_LIBRARY_PATH=${CUDA_HOME}/lib64:$LD_LIBRARY_PATH - -# Install uv and source its environment -RUN curl -LsSf https://astral.sh/uv/install.sh | sh && \ - echo 'source $HOME/.local/bin/env' >> /root/.bashrc - -# Copy just the pyproject.toml first to leverage Docker cache -COPY pyproject.toml ./ - -# Create a dummy README to satisfy the installation -RUN echo "# Placeholder" > README.md - -# Create and activate virtual environment with specific Python version and seed -RUN source $HOME/.local/bin/env && \ - uv venv --python 3.10 --seed /opt/venv && \ - source /opt/venv/bin/activate && \ - uv pip install --no-cache-dir --upgrade pip && \ - uv pip install --no-cache-dir ".[dev]" && \ - uv pip install --no-cache-dir https://github.com/mjun0812/flash-attention-prebuild-wheels/releases/download/v0.9.4/flash_attn-2.8.3+cu128torch2.11-cp310-cp310-linux_x86_64.whl - -COPY . . - -# Install dependencies using uv and set up shell configuration -RUN source $HOME/.local/bin/env && \ - source /opt/venv/bin/activate && \ - uv pip install --no-cache-dir -e ".[dev]" && \ - git config --unset-all http.https://github.com/.extraheader || true && \ - echo 'source /opt/venv/bin/activate' >> /root/.bashrc && \ - echo 'if [ -n "$ZSH_VERSION" ] && [ -f ~/.zshrc ]; then . ~/.zshrc; elif [ -f ~/.bashrc ]; then . ~/.bashrc; fi' > /root/.profile - -# Install FastVideo Unified Kernel. -# This build machine has no GPU, so target Hopper (sm_90a) explicitly instead -# of probing a live device for the arch (matches the released kernel wheel). -RUN source $HOME/.local/bin/env && \ - source /opt/venv/bin/activate && \ - cd fastvideo-kernel && \ - git submodule update --init --recursive && \ - TORCH_CUDA_ARCH_LIST=9.0a ./build.sh - - -EXPOSE 22 diff --git a/docker/Dockerfile.python3.11 b/docker/Dockerfile.python3.11 deleted file mode 100644 index 6aa541bd8..000000000 --- a/docker/Dockerfile.python3.11 +++ /dev/null @@ -1,68 +0,0 @@ -FROM nvidia/cuda:12.8.0-cudnn-devel-ubuntu22.04 - -ENV DEBIAN_FRONTEND=noninteractive - -SHELL ["/bin/bash", "-c"] - -WORKDIR /FastVideo - -RUN apt-get update && apt-get install -y --no-install-recommends \ - wget \ - git \ - ca-certificates \ - openssh-server \ - zsh \ - vim \ - curl \ - gcc-11 \ - g++-11 \ - clang-11 \ - && rm -rf /var/lib/apt/lists/* - -# Set up C++20 compilers for ThunderKittens -RUN update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-11 100 --slave /usr/bin/g++ g++ /usr/bin/g++-11 - -# Set CUDA environment variables -ENV CUDA_HOME=/usr/local/cuda-12.8 -ENV PATH=${CUDA_HOME}/bin:${PATH} -ENV LD_LIBRARY_PATH=${CUDA_HOME}/lib64:$LD_LIBRARY_PATH - -# Install uv and source its environment -RUN curl -LsSf https://astral.sh/uv/install.sh | sh && \ - echo 'source $HOME/.local/bin/env' >> /root/.bashrc - -# Copy just the pyproject.toml first to leverage Docker cache -COPY pyproject.toml ./ - -# Create a dummy README to satisfy the installation -RUN echo "# Placeholder" > README.md - -# Create and activate virtual environment with specific Python version and seed -RUN source $HOME/.local/bin/env && \ - uv venv --python 3.11 --seed /opt/venv && \ - source /opt/venv/bin/activate && \ - uv pip install --no-cache-dir --upgrade pip && \ - uv pip install --no-cache-dir ".[dev]" && \ - uv pip install --no-cache-dir https://github.com/mjun0812/flash-attention-prebuild-wheels/releases/download/v0.9.4/flash_attn-2.8.3+cu128torch2.11-cp311-cp311-linux_x86_64.whl - -COPY . . - -# Install dependencies using uv and set up shell configuration -RUN source $HOME/.local/bin/env && \ - source /opt/venv/bin/activate && \ - uv pip install --no-cache-dir -e ".[dev]" && \ - git config --unset-all http.https://github.com/.extraheader || true && \ - echo 'source /opt/venv/bin/activate' >> /root/.bashrc && \ - echo 'if [ -n "$ZSH_VERSION" ] && [ -f ~/.zshrc ]; then . ~/.zshrc; elif [ -f ~/.bashrc ]; then . ~/.bashrc; fi' > /root/.profile - -# Install FastVideo Unified Kernel. -# This build machine has no GPU, so target Hopper (sm_90a) explicitly instead -# of probing a live device for the arch (matches the released kernel wheel). -RUN source $HOME/.local/bin/env && \ - source /opt/venv/bin/activate && \ - cd fastvideo-kernel && \ - git submodule update --init --recursive && \ - TORCH_CUDA_ARCH_LIST=9.0a ./build.sh - - -EXPOSE 22 diff --git a/docker/Dockerfile.python3.12 b/docker/Dockerfile.python3.12 deleted file mode 100644 index 5ac93f392..000000000 --- a/docker/Dockerfile.python3.12 +++ /dev/null @@ -1,67 +0,0 @@ -FROM nvidia/cuda:12.8.0-cudnn-devel-ubuntu22.04 - -ENV DEBIAN_FRONTEND=noninteractive - -SHELL ["/bin/bash", "-c"] - -WORKDIR /FastVideo - -RUN apt-get update && apt-get install -y --no-install-recommends \ - wget \ - git \ - ca-certificates \ - openssh-server \ - zsh \ - vim \ - curl \ - gcc-11 \ - g++-11 \ - clang-11 \ - && rm -rf /var/lib/apt/lists/* - -# Set up C++20 compilers for ThunderKittens -RUN update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-11 100 --slave /usr/bin/g++ g++ /usr/bin/g++-11 - -# Set CUDA environment variables -ENV CUDA_HOME=/usr/local/cuda-12.8 -ENV PATH=${CUDA_HOME}/bin:${PATH} -ENV LD_LIBRARY_PATH=${CUDA_HOME}/lib64:$LD_LIBRARY_PATH - -# Install uv and source its environment -RUN curl -LsSf https://astral.sh/uv/install.sh | sh && \ - echo 'source $HOME/.local/bin/env' >> /root/.bashrc - -# Copy just the pyproject.toml first to leverage Docker cache -COPY pyproject.toml ./ - -# Create a dummy README to satisfy the installation -RUN echo "# Placeholder" > README.md - -# Create and activate virtual environment with specific Python version and seed -RUN source $HOME/.local/bin/env && \ - uv venv --python 3.12 --seed /opt/venv && \ - source /opt/venv/bin/activate && \ - uv pip install --no-cache-dir --upgrade pip && \ - uv pip install --no-cache-dir ".[dev]" && \ - uv pip install --no-cache-dir https://github.com/mjun0812/flash-attention-prebuild-wheels/releases/download/v0.9.4/flash_attn-2.8.3+cu128torch2.11-cp312-cp312-linux_x86_64.whl - -COPY . . - -# Install dependencies using uv and set up shell configuration -RUN source $HOME/.local/bin/env && \ - source /opt/venv/bin/activate && \ - uv pip install --no-cache-dir -e ".[dev]" && \ - git config --unset-all http.https://github.com/.extraheader || true && \ - echo 'source /opt/venv/bin/activate' >> /root/.bashrc && \ - echo 'if [ -n "$ZSH_VERSION" ] && [ -f ~/.zshrc ]; then . ~/.zshrc; elif [ -f ~/.bashrc ]; then . ~/.bashrc; fi' > /root/.profile - -# Install FastVideo Unified Kernel. -# This build machine has no GPU, so target Hopper (sm_90a) explicitly instead -# of probing a live device for the arch (matches the released kernel wheel). -RUN source $HOME/.local/bin/env && \ - source /opt/venv/bin/activate && \ - cd fastvideo-kernel && \ - git submodule update --init --recursive && \ - TORCH_CUDA_ARCH_LIST=9.0a ./build.sh - -EXPOSE 22 diff --git a/docker/Dockerfile.python3.12.cuda12.9.1 b/docker/Dockerfile.python3.12.cuda12.9.1 deleted file mode 100644 index 3f1830d72..000000000 --- a/docker/Dockerfile.python3.12.cuda12.9.1 +++ /dev/null @@ -1,68 +0,0 @@ -FROM nvidia/cuda:12.9.1-cudnn-devel-ubuntu22.04 - -ENV DEBIAN_FRONTEND=noninteractive - -SHELL ["/bin/bash", "-c"] - -WORKDIR /FastVideo - -RUN apt-get update && apt-get install -y --no-install-recommends \ - wget \ - git \ - ca-certificates \ - openssh-server \ - zsh \ - vim \ - curl \ - gcc-11 \ - g++-11 \ - clang-11 \ - && rm -rf /var/lib/apt/lists/* - -# Set up C++20 compilers for ThunderKittens -RUN update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-11 100 --slave /usr/bin/g++ g++ /usr/bin/g++-11 - -# Set CUDA environment variables -ENV CUDA_HOME=/usr/local/cuda-12.9 -ENV PATH=${CUDA_HOME}/bin:${PATH} -ENV LD_LIBRARY_PATH=${CUDA_HOME}/lib64:$LD_LIBRARY_PATH - -# Install uv and source its environment -RUN curl -LsSf https://astral.sh/uv/install.sh | sh && \ - echo 'source $HOME/.local/bin/env' >> /root/.bashrc - -# Copy just the pyproject.toml first to leverage Docker cache -COPY pyproject.toml ./ - -# Create a dummy README to satisfy the installation -RUN echo "# Placeholder" > README.md - -# Create and activate virtual environment with specific Python version and seed -RUN source $HOME/.local/bin/env && \ - uv venv --python 3.12 --seed /opt/venv && \ - source /opt/venv/bin/activate && \ - uv pip install --no-cache-dir --upgrade pip && \ - uv pip install --no-cache-dir ".[dev]" && \ - uv pip install --no-cache-dir flash-attn==2.8.3 --no-build-isolation - -COPY . . - -# Install dependencies using uv and set up shell configuration -RUN source $HOME/.local/bin/env && \ - source /opt/venv/bin/activate && \ - uv pip install --no-cache-dir -e ".[dev]" && \ - git config --unset-all http.https://github.com/.extraheader || true && \ - echo 'source /opt/venv/bin/activate' >> /root/.bashrc && \ - echo 'if [ -n "$ZSH_VERSION" ] && [ -f ~/.zshrc ]; then . ~/.zshrc; elif [ -f ~/.bashrc ]; then . ~/.bashrc; fi' > /root/.profile - -# Install FastVideo Unified Kernel. -# This build machine has no GPU, so target Hopper (sm_90a) explicitly instead -# of probing a live device for the arch (matches the released kernel wheel). -RUN source $HOME/.local/bin/env && \ - source /opt/venv/bin/activate && \ - cd fastvideo-kernel && \ - git submodule update --init --recursive && \ - TORCH_CUDA_ARCH_LIST=9.0a ./build.sh - - -EXPOSE 22 diff --git a/docker/uv-excludes b/docker/uv-excludes new file mode 100644 index 000000000..7ceab7dbd --- /dev/null +++ b/docker/uv-excludes @@ -0,0 +1,3 @@ +# fastvideo-kernel is compiled once from the complete source tree after the +# cached dependency layer. In particular, do not PEP 517-build it here on ARM. +fastvideo-kernel diff --git a/docs/attention/index.md b/docs/attention/index.md index f11744901..90b723649 100644 --- a/docs/attention/index.md +++ b/docs/attention/index.md @@ -19,7 +19,7 @@ source, which includes both STA and VSA kernels. ### Prerequisites * **PyTorch**: 2.5.0+ -* **CUDA**: 12.4+ (12.8 recommended for best performance) +* **CUDA**: 12.6+ (CUDA 13 recommended for Blackwell GPUs) * **C++ Compiler**: GCC 11+ (C++20 support required for ThunderKittens) Install system dependencies: @@ -35,7 +35,7 @@ sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-11 100 --slave Set up your CUDA environment variables (adjust version as needed): ```bash -export CUDA_HOME=/usr/local/cuda-12.8 +export CUDA_HOME=/usr/local/cuda export PATH=${CUDA_HOME}/bin:${PATH} export LD_LIBRARY_PATH=${CUDA_HOME}/lib64:$LD_LIBRARY_PATH ``` diff --git a/docs/contributing/ci_architecture.md b/docs/contributing/ci_architecture.md index 3d6761c1c..56d73d012 100644 --- a/docs/contributing/ci_architecture.md +++ b/docs/contributing/ci_architecture.md @@ -80,11 +80,11 @@ status. | Buildkite label | `TEST_TYPE` | Main watched paths | |---|---|---| -| Encoder Tests | `encoder` | `fastvideo/models/encoders/**`, `fastvideo/models/loader/**`, `fastvideo/tests/encoders/**`, `pyproject.toml`, `docker/Dockerfile.python3.12` | -| VAE Tests | `vae` | `fastvideo/models/vaes/**`, `fastvideo/models/loader/**`, `fastvideo/tests/vaes/**`, `pyproject.toml`, `docker/Dockerfile.python3.12` | -| Transformer Tests | `transformer` | `fastvideo/models/dits/**`, `fastvideo/models/loader/**`, `fastvideo/tests/transformers/**`, `fastvideo/layers/**`, `fastvideo/attention/**`, `pyproject.toml`, `docker/Dockerfile.python3.12` | -| Kernel Tests | `kernel_tests` | `fastvideo-kernel/**`, `pyproject.toml`, `docker/Dockerfile.python3.12` | -| Unit Tests | `unit_test` | `fastvideo/**`, `.buildkite/**`, `.github/**`, `pyproject.toml`, `docker/Dockerfile.python3.12` | +| Encoder Tests | `encoder` | `fastvideo/models/encoders/**`, `fastvideo/models/loader/**`, `fastvideo/tests/encoders/**`, `pyproject.toml`, `docker/Dockerfile` | +| VAE Tests | `vae` | `fastvideo/models/vaes/**`, `fastvideo/models/loader/**`, `fastvideo/tests/vaes/**`, `pyproject.toml`, `docker/Dockerfile` | +| Transformer Tests | `transformer` | `fastvideo/models/dits/**`, `fastvideo/models/loader/**`, `fastvideo/tests/transformers/**`, `fastvideo/layers/**`, `fastvideo/attention/**`, `pyproject.toml`, `docker/Dockerfile` | +| Kernel Tests | `kernel_tests` | `fastvideo-kernel/**`, `pyproject.toml`, `docker/Dockerfile` | +| Unit Tests | `unit_test` | `fastvideo/**`, `.buildkite/**`, `.github/**`, `pyproject.toml`, `docker/Dockerfile` | | DreamVerse App Tests | `dreamverse_app` | `apps/dreamverse/**`, `pyproject.toml` | ### Tier 3: Full Suite @@ -101,18 +101,18 @@ can merge a PR. | Buildkite label | `TEST_TYPE` | Main watched paths | |---|---|---| -| SSIM Tests | `ssim` | `fastvideo/**/*.py`, `pyproject.toml`, `docker/Dockerfile.python3.12` | +| SSIM Tests | `ssim` | `fastvideo/**/*.py`, `pyproject.toml`, `docker/Dockerfile` | | LoRA Inference Tests | `inference_lora` | LoRA tests, loader, transformer tests, pipelines, LoRA layers | -| Training Tests | `training` | `fastvideo/**`, `pyproject.toml`, `docker/Dockerfile.python3.12` | +| Training Tests | `training` | `fastvideo/**`, `pyproject.toml`, `docker/Dockerfile` | | Distillation DMD Tests | `distillation_dmd` | `fastvideo/training/*distillation_pipeline.py` | | Self-Forcing Tests | `self_forcing` | self-forcing distillation pipeline and tests | -| LoRA Training Tests | `training_lora` | `fastvideo/**`, `pyproject.toml`, `docker/Dockerfile.python3.12` | -| Training Tests VSA | `training_vsa` | `fastvideo/**`, `fastvideo-kernel/**`, `pyproject.toml`, `docker/Dockerfile.python3.12` | +| LoRA Training Tests | `training_lora` | `fastvideo/**`, `pyproject.toml`, `docker/Dockerfile` | +| Training Tests VSA | `training_vsa` | `fastvideo/**`, `fastvideo-kernel/**`, `pyproject.toml`, `docker/Dockerfile` | | Inference Tests VMoBA | `inference_vmoba` | `fastvideo-kernel/**`, `fastvideo/attention/backends/vmoba.py` | | Performance Tests | `performance` | DiTs, pipelines, attention, layers, worker, entrypoints, performance tests/configs | | API Server Tests | `api_server` | OpenAI entrypoints, serve CLI, OpenAI API integration test | | Train Framework Tests | `train_framework` | `fastvideo/train/**`, train model/method tests, model loader, DiTs | -| Eval Metrics Tests | `eval` | `fastvideo/eval/**`, `fastvideo/tests/eval/**`, `pyproject.toml`, `docker/Dockerfile.python3.12` | +| Eval Metrics Tests | `eval` | `fastvideo/eval/**`, `fastvideo/tests/eval/**`, `pyproject.toml`, `docker/Dockerfile` | See [Performance Benchmarks](performance_benchmarks.md) for the performance lane's thresholds, rolling baseline, artifacts, and reseeding process. @@ -260,8 +260,18 @@ The docs job: ### Docker Images `.github/workflows/infra-build-image.yml` is a manual `workflow_dispatch` -workflow. Maintainers choose which image families to build, including Python -development images and DreamVerse CUDA 12.9 images. +workflow. Maintainers choose which image families to build. The +`fastvideo-dev` matrix builds Python 3.12 images for CUDA 12.6 and CUDA 13 on +native `amd64` and `arm64` runners, then publishes one multi-platform manifest +per CUDA version. CUDA 12.6 owns the `py3.12-latest` and global `latest` tags, +as well as the explicit `py3.12-cuda12.6.3-latest` alias. CUDA 13 is published +under the explicit `py3.12-cuda13.0.0-latest` tag. This publication policy does +not change the unparameterized `docker/Dockerfile` build defaults, which remain +CUDA 13 and `cu130`. + +The optional Dreamverse matrix builds backend and UI images for CUDA 12.6 and +CUDA 13 on `amd64`. Dreamverse remains `amd64`-only because its FA4 dependency +stack is not yet validated on ARM64. The reusable implementation lives in `.github/workflows/_template-build-image.yml`. diff --git a/docs/contributing/developer_env/docker.md b/docs/contributing/developer_env/docker.md index 0de910e81..e5ef73fff 100644 --- a/docs/contributing/developer_env/docker.md +++ b/docs/contributing/developer_env/docker.md @@ -5,6 +5,20 @@ If you prefer a containerized development environment or want to avoid managing **Images:** [`ghcr.io/hao-ai-lab/fastvideo/fastvideo-dev:py3.12-latest`](https://ghcr.io/hao-ai-lab/fastvideo/fastvideo-dev) +The published FastVideo tags are multi-platform Linux images for `amd64` and +`arm64`; Docker automatically pulls the matching architecture. The +`py3.12-latest` and global `latest` tags select CUDA 12.6.3 with the `cu126` +PyTorch backend. `py3.12-cuda12.6.3-latest` is the explicit alias for that +same image; use `py3.12-cuda13.0.0-latest` for CUDA 13 with `cu130`. + +On ARM64, the CUDA 13 image targets DGX Spark (`sm_121`), while the CUDA 12.6 +image targets GH200-class hardware (`sm_90a`). These published-tag defaults +are distinct from an unparameterized local `docker build`: `docker/Dockerfile` +itself still defaults to CUDA 13 and `cu130`. + +DGX Spark users must therefore select `py3.12-cuda13.0.0-latest`; the default +CUDA 12.6 ARM64 image does not contain an `sm_121` FastVideo kernel. + ## Starting the container ```bash @@ -14,12 +28,26 @@ docker run --gpus all -it ghcr.io/hao-ai-lab/fastvideo/fastvideo-dev:py3.12-late This will: - Start the container with GPU access -- Drop you into a shell with the `fastvideo-dev` Conda environment preconfigured +- Drop you into a shell with the FastVideo virtual environment activated + +## Building locally + +The same Dockerfile builds both architectures and defaults to CUDA 13. On a +native ARM64 host, build the CUDA 13 DGX Spark image with: + +```bash +docker build --platform linux/arm64 -f docker/Dockerfile \ + --build-arg TORCH_CUDA_ARCH_LIST=12.1 \ + -t fastvideo-dev:spark . +``` + +This uses the prebuilt Linux ARM64 FlashAttention wheel. FastVideo's in-tree +CUDA kernel is still compiled for `sm_121` as part of the image build. ## Using the container ```bash -# Conda environment should already be active +# The FastVideo virtual environment should already be active # FastVideo package installed in editable mode # Pull the latest changes from remote diff --git a/docs/contributing/developer_env/runpod.md b/docs/contributing/developer_env/runpod.md index 253e2b7fe..5dd5f8aff 100644 --- a/docs/contributing/developer_env/runpod.md +++ b/docs/contributing/developer_env/runpod.md @@ -18,7 +18,8 @@ Make sure you're logged into the right RunPod account before spending credits. **2. Filter by CUDA version** -Use "Additional Filters" to select CUDA 12.8. +Use "Additional Filters" to select a CUDA version supported by the FastVideo +template. ![RunPod CUDA selection](../../assets/images/runpod_cuda.png) @@ -68,6 +69,10 @@ If you're setting up a pod from scratch instead of the FastVideo template, use t ghcr.io/hao-ai-lab/fastvideo/fastvideo-dev:py3.12-latest ``` +This default tag uses CUDA 12.6.3 with the `cu126` PyTorch backend. Use +`py3.12-cuda13.0.0-latest` instead when the pod specifically needs the CUDA 13 +and `cu130` image. + And paste this as the Container Start Command to enable SSH ([RunPod docs](https://docs.runpod.io/pods/configuration/use-ssh)): ```bash @@ -102,7 +107,8 @@ cd /FastVideo uv pip install -e ".[dev]" ``` -The Docker image already includes Flash Attention and most heavy dependencies, so this is fast. +This preserves the PyTorch backend selected by the Docker image. The image also +includes Flash Attention and most heavy dependencies, so this is fast. **Build the custom kernels (optional)** diff --git a/docs/contributing/dreamverse-development.md b/docs/contributing/dreamverse-development.md index 4ac195df8..e9228295c 100644 --- a/docs/contributing/dreamverse-development.md +++ b/docs/contributing/dreamverse-development.md @@ -30,7 +30,7 @@ If `dreamverse-server` is missing, install FastVideo with the `dreamverse` extra from the checkout: ```bash -uv pip install -e ".[dreamverse]" +UV_TORCH_BACKEND=cu126 uv pip install -e ".[dreamverse]" # use cu130 on CUDA 13 ``` ## Frontend build and tests diff --git a/docs/contributing/overview.md b/docs/contributing/overview.md index ea9ff60f1..5e167dcb5 100644 --- a/docs/contributing/overview.md +++ b/docs/contributing/overview.md @@ -49,7 +49,7 @@ git clone https://github.com/hao-ai-lab/FastVideo.git && cd FastVideo Install FastVideo in editable mode and set up hooks: ```bash -uv pip install -e ".[dev]" +UV_TORCH_BACKEND=cu126 uv pip install -e ".[dev]" # use cu130 on CUDA 13 # Optional: FlashAttention (builds native kernels) uv pip install flash-attn --no-build-isolation -v diff --git a/docs/getting_started/installation.md b/docs/getting_started/installation.md index 9d3fe5316..cfda9fac2 100644 --- a/docs/getting_started/installation.md +++ b/docs/getting_started/installation.md @@ -4,6 +4,7 @@ FastVideo supports the following hardware platforms: - [NVIDIA CUDA](installation/gpu.md) +- [NVIDIA DGX Spark / GB10 (ARM64 + CUDA 13)](installation/spark.md) - [Apple silicon](installation/mps.md) ## Quick Installation @@ -11,13 +12,15 @@ FastVideo supports the following hardware platforms: ### Using uv (recommended) Use uv as the default environment manager for faster and more stable installs. +The commands below target NVIDIA CUDA 12; use `UV_TORCH_BACKEND=cu130` on +CUDA 13. Apple silicon users should follow the [MPS guide](installation/mps.md). ```bash # Create and activate a new uv environment uv venv --python 3.12 --seed source .venv/bin/activate -uv pip install fastvideo +UV_TORCH_BACKEND=cu126 uv pip install fastvideo ``` ### Using Conda (alternative) @@ -27,15 +30,18 @@ uv pip install fastvideo conda create -n fastvideo python=3.12 -y conda activate fastvideo -uv pip install fastvideo +UV_TORCH_BACKEND=cu126 uv pip install fastvideo ``` ### From source +FastVideo pins PyTorch 2.12.0. Select its CUDA build explicitly with +`UV_TORCH_BACKEND=cu126` for CUDA 12 or `UV_TORCH_BACKEND=cu130` for CUDA 13. + ```bash git clone https://github.com/hao-ai-lab/FastVideo.git cd FastVideo -uv pip install -e . +UV_TORCH_BACKEND=cu126 uv pip install -e . # optional: install flash-attn uv pip install flash-attn --no-build-isolation -v @@ -44,14 +50,14 @@ uv pip install flash-attn --no-build-isolation -v Alternative with Conda environment (still drives installs through `uv`): ```bash -uv pip install -e . +UV_TORCH_BACKEND=cu126 uv pip install -e . uv pip install flash-attn --no-build-isolation -v ``` ## Hardware Requirements -- **NVIDIA GPUs**: CUDA 11.8+ with compute capability 7.0+ -- **Apple Silicon**: macOS 12.0+ with M1/M2/M3 chips +- **NVIDIA GPUs**: CUDA 12.6+ with compute capability 7.0+ +- **Apple Silicon**: macOS 14.0+ with M1/M2/M3/M4 chips - **CPU**: x86_64 architecture (for CPU-only inference) ## Next Steps diff --git a/docs/getting_started/installation/gpu.md b/docs/getting_started/installation/gpu.md index 1e06b1915..bd9ee9496 100644 --- a/docs/getting_started/installation/gpu.md +++ b/docs/getting_started/installation/gpu.md @@ -6,7 +6,7 @@ Instructions to install FastVideo for NVIDIA CUDA GPUs. - **OS: Linux or Windows WSL** - **Python: 3.10-3.12** -- **CUDA 12.8** +- **CUDA: 12.6 or 13.0** - **At least 1 NVIDIA GPU** ## Set up using Python @@ -47,7 +47,7 @@ conda activate fastvideo #### With uv (recommended) ```bash -uv pip install fastvideo +UV_TORCH_BACKEND=cu126 uv pip install fastvideo ``` Also optionally install FlashAttention: @@ -61,7 +61,7 @@ uv pip install flash-attn --no-build-isolation -v `uv` works inside an active conda env too, so prefer `uv pip` for the actual install: ```bash -uv pip install fastvideo +UV_TORCH_BACKEND=cu126 uv pip install fastvideo ``` Also optionally install FlashAttention: @@ -80,16 +80,19 @@ git clone https://github.com/hao-ai-lab/FastVideo.git && cd FastVideo #### 2. Install FastVideo +FastVideo requires PyTorch 2.12.0. Use `UV_TORCH_BACKEND=cu126` for CUDA 12 or +`UV_TORCH_BACKEND=cu130` for CUDA 13. + Basic installation: ```bash -uv pip install -e . +UV_TORCH_BACKEND=cu126 uv pip install -e . ``` Alternative with Conda environment: ```bash -uv pip install -e . +UV_TORCH_BACKEND=cu126 uv pip install -e . ``` ### Optional Dependencies @@ -118,7 +121,7 @@ If you're planning to contribute to FastVideo please see the following page: ## Hardware Requirements ### For Basic Inference -- NVIDIA GPU with CUDA 12.8 support +- NVIDIA GPU compatible with CUDA 12.6 or newer ### For Lora Finetuning - 40GB GPU memory each for 2 GPUs with lora diff --git a/docs/getting_started/installation/mps.md b/docs/getting_started/installation/mps.md index d3d377b08..df80c38a5 100644 --- a/docs/getting_started/installation/mps.md +++ b/docs/getting_started/installation/mps.md @@ -4,7 +4,7 @@ Instructions to install FastVideo for Apple Silicon. ## Requirements -- **OS: MacOS** +- **OS: macOS 14 or newer** - **Python: 3.12.4** ## Set up using Python diff --git a/docs/getting_started/installation/spark.md b/docs/getting_started/installation/spark.md new file mode 100644 index 000000000..4b5e9c528 --- /dev/null +++ b/docs/getting_started/installation/spark.md @@ -0,0 +1,143 @@ +# NVIDIA DGX Spark (GB10) + +Instructions to install FastVideo on an **NVIDIA DGX Spark** — the GB10 +Grace-Blackwell platform. + +The Spark is **ARM64 (`aarch64`) with CUDA 13**, which the standard +[NVIDIA GPU guide](gpu.md) does not cover: there is no prebuilt `aarch64` wheel +for `fastvideo-kernel`, so it is **compiled from source** as part of the +install, and the system Python typically lacks the development headers that +build needs. The steps below handle both, **without requiring `sudo`**. + +## Requirements + +- **OS: Linux (`aarch64`)** +- **GPU: NVIDIA GB10** (compute capability `sm_121`), at least 1 +- **CUDA Toolkit: 13.x** at `/usr/local/cuda` (`nvcc` on `PATH`) +- **Python: 3.10–3.12** +- **Compilers:** gcc/g++ 10+ (the Spark ships gcc 13) + +!!! note "Different compute capability?" + These instructions target the GB10's `sm_121`. The kernel build auto-detects + it from your GPU, so there is normally nothing to set. To check your device: + ```bash + python -c "import torch; print(torch.cuda.get_device_capability(0))" # GB10 -> (12, 1) + ``` + +## Install + +Run from the repository root: + +```bash +# 0. (once) install uv and put it on PATH +curl -LsSf https://astral.sh/uv/install.sh | sh +export PATH="$HOME/.local/bin:$PATH" +git clone https://github.com/hao-ai-lab/FastVideo.git && cd FastVideo + +# 1. Create a venv on a uv-managed CPython 3.12 (bundles the dev headers the +# kernel build needs; the Spark's system python3.12 lacks them). +uv venv .venv --python 3.12 --python-preference only-managed --seed +source .venv/bin/activate + +# 2. Initialise the kernel submodules (cutlass + ThunderKittens headers). +git submodule update --init --recursive \ + fastvideo-kernel/include/cutlass fastvideo-kernel/include/tk + +# 3. Install FastVideo (editable; compiles the in-tree kernel for the GB10). +UV_TORCH_BACKEND=cu130 uv pip install -e . +``` + +Contributors who want the lint/test tooling: +`UV_TORCH_BACKEND=cu130 uv pip install -e ".[dev]"`. + +Then jump to [Verify the install](#verify-the-install). + +## Building without a visible GPU (CI / Docker) + +With no GPU visible the kernel build can't probe the arch and `auto` can't detect +the driver — name both explicitly: + +```bash +UV_TORCH_BACKEND=cu130 TORCH_CUDA_ARCH_LIST=12.1 uv pip install -e . +``` + +## Verify the install + +```bash +python - <<'PY' +import torch, fastvideo +print("fastvideo:", fastvideo.__version__) +print("torch :", torch.__version__, "| cuda:", torch.version.cuda, "| avail:", torch.cuda.is_available()) +print("device :", torch.cuda.get_device_name(0)) +PY + +fastvideo --help +``` + +Expected: a `+cu130` torch, `device: NVIDIA GB10`, and the CLI listing +`generate / serve / router-serve / bench / eval`. + +To confirm the **compiled** CUDA kernel actually runs on the GB10 (importing +alone doesn't execute the `.so`): + +```bash +python - <<'PY' +import torch +from fastvideo_kernel import Int8Linear +lin = torch.nn.Linear(512, 256, bias=False).cuda().to(torch.bfloat16) +# .cuda() is required: from_linear() leaves the int8 buffers on the CPU +qlin = Int8Linear.from_linear(lin, quantize=True).cuda() # compiled quant_cuda +x = torch.randn(128, 512, device="cuda", dtype=torch.bfloat16) +y, ref = qlin(x), lin(x) # compiled gemm_cuda +rel = (y.float() - ref.float()).norm() / ref.float().norm() +print(f"int8 GEMM rel err vs fp32: {rel.item():.4f} (~0.01 is correct; int8 is lossy)") +PY +``` + +## Manual build (advanced / fallback) + +The one-liner above is the supported path. Build the kernel yourself only if you +are iterating on the CUDA source or the auto-build fails: + +```bash +# install torch + the kernel's build deps into the active venv first +UV_TORCH_BACKEND=cu130 uv pip install torch torchvision torchaudio scikit-build-core cmake ninja setuptools wheel +# build just the kernel against that torch (auto-detects sm_121) +uv pip install ./fastvideo-kernel --no-build-isolation +# then the rest of FastVideo +uv pip install -e . +``` + +Or `cd fastvideo-kernel && ./build.sh`, which auto-detects the arch and does the +same compile (it initializes only the kernel's `cutlass` and `tk` submodules). + +## Optional: flash-attn + +Not installed by default. FastVideo falls back to other attention backends +without it. For the guide's Python 3.12, PyTorch 2.12, and CUDA 13 environment, +install the matching prebuilt Linux ARM64 wheel from the +[FlashAttention prebuilt wheel index](https://mjunya.com/flash-attention-prebuild-wheels/?python=3.12&torch=2.12&cuda=13.0&platform=Linux+arm64): + +```bash +uv pip install "https://github.com/mjun0812/flash-attention-prebuild-wheels/releases/download/v0.9.22/flash_attn-2.8.3%2Bcu130torch2.12-cp312-cp312-linux_aarch64.whl" +``` + +## Troubleshooting + +| Symptom | Cause / Fix | +|---|---| +| `Could NOT find Python (missing: ... Development.Module)` | venv built from system Python without headers. Recreate with `--python-preference only-managed` (add `--clear` to reuse the path), or `sudo apt install python3.12-dev`. | +| kernel build can't find cutlass headers | Submodules not initialised — run the `git submodule update` step. | +| `fastvideo-kernel: could not determine the target CUDA architecture` | The build couldn't see a GPU and no arch was given. Build on the Spark itself, or pass `TORCH_CUDA_ARCH_LIST=12.1` (see [Building without a visible GPU](#building-without-a-visible-gpu-ci--docker)). | +| `nvcc fatal: Unsupported gpu architecture 'compute_121'` | `nvcc` older than CUDA 12.9/13. Confirm `nvcc --version` is 13.x and `CUDACXX=/usr/local/cuda/bin/nvcc`. | +| `ninja: command not found` (manual build only) | `uv pip install scikit-build-core cmake ninja setuptools wheel`. | + +If you hit other issues, please open an issue on our +[GitHub repository](https://github.com/hao-ai-lab/FastVideo). You can also join +our [Slack community](https://join.slack.com/t/fastvideo/shared_invite/zt-38u6p1jqe-yDI1QJOCEnbtkLoaI5bjZQ) +for additional support. + +## Development Environment Setup + +If you're planning to contribute to FastVideo please see the +[Contributor Guide](../../contributing/overview.md). diff --git a/docs/getting_started/quick_start.md b/docs/getting_started/quick_start.md index 9b754f58a..644f0e672 100644 --- a/docs/getting_started/quick_start.md +++ b/docs/getting_started/quick_start.md @@ -11,10 +11,12 @@ First, install FastVideo: uv venv --python 3.12 --seed source .venv/bin/activate -# Install FastVideo -uv pip install fastvideo +# Install FastVideo on NVIDIA CUDA 12 +UV_TORCH_BACKEND=cu126 uv pip install fastvideo ``` +Use `UV_TORCH_BACKEND=cu130` instead on CUDA 13. + Also optionally install flash-attn: ```bash diff --git a/docs/inference/inference_quick_start.md b/docs/inference/inference_quick_start.md index 4a647ab48..c07c3c770 100644 --- a/docs/inference/inference_quick_start.md +++ b/docs/inference/inference_quick_start.md @@ -6,7 +6,7 @@ This page contains step-by-step instructions to get you quickly started with vid - **OS**: Linux (Tested on Ubuntu 22.04+) - **Python**: 3.10-3.12 -- **CUDA**: 12.8 +- **CUDA**: 12.6 or 13.0 - **GPU**: At least one NVIDIA GPU ## Installation @@ -19,9 +19,11 @@ uv venv --python 3.12 --seed source .venv/bin/activate # Install FastVideo -uv pip install fastvideo +UV_TORCH_BACKEND=cu126 uv pip install fastvideo ``` +Use `UV_TORCH_BACKEND=cu130` instead on CUDA 13. + For advanced installation options, see the [Installation Guide](../getting_started/installation.md). ## Generating Your First Video diff --git a/docs/inference/optimizations.md b/docs/inference/optimizations.md index 23f164e68..df8d5f4c4 100644 --- a/docs/inference/optimizations.md +++ b/docs/inference/optimizations.md @@ -85,7 +85,7 @@ See the [Attn-QAT paper](https://arxiv.org/abs/2603.00040) and [flash-attention- #### Requirements - **GPU**: NVIDIA Blackwell (sm100a or sm103a) — B200, B300, GB200, GB300 -- **CUDA**: 12.8+ +- **CUDA**: 13.0+ - **Python**: 3.10 or 3.11 #### Installation @@ -93,11 +93,14 @@ See the [Attn-QAT paper](https://arxiv.org/abs/2603.00040) and [flash-attention- Install the FP4 flash attention kernel (without upgrading your existing torch): ```bash -pip install --no-deps "git+ssh://git@github.com/hao-ai-lab/flash-attention-fp4.git@fp4#subdirectory=flash_attn/cute" -pip install "nvidia-cutlass-dsl>=4.4.2" apache-tvm-ffi flashinfer-python +# branch fix/cutlass-dsl-4.5 carries the cutlass-dsl 4.5 fix (cute.core.ThrMma +# -> cute.ThrMma); switch back to @fp4 once hao-ai-lab/flash-attention-fp4#2 merges. +pip install --no-deps "git+ssh://git@github.com/hao-ai-lab/flash-attention-fp4.git@fix/cutlass-dsl-4.5#subdirectory=flash_attn/cute" +pip install "nvidia-cutlass-dsl>=4.5.2" apache-tvm-ffi flashinfer-python ``` -The `--no-deps` flag prevents upgrading torch/torchvision. The kernel requires torch >= 2.4 with CUDA 12.8+ support (already present in FastVideo's environment). +The `--no-deps` flag prevents upgrading torch/torchvision. Use the supported +PyTorch 2.12.0 and CUDA 13 environment for this kernel. #### Usage @@ -211,7 +214,9 @@ python setup.py install # or uv pip install -e . #### Installation -Note that Sage Attention 3 requires `python>=3.13`, `torch>=2.8.0`, `CUDA >=12.8`. If you are using `uv` and using `torch==2.8.0` make sure that `sentencepiece==0.2.1` in the pyproject.toml file. +Note that Sage Attention 3 requires `python>=3.13`, `torch>=2.8.0`, and CUDA 13. +If you are using `uv` and `torch==2.8.0`, make sure that +`sentencepiece==0.2.1` in the `pyproject.toml` file. To use Sage Attention 3 in FastVideo, follow the `README.md` in the linked repository to install the package from source. diff --git a/examples/inference/basic/basic_dmd.py b/examples/inference/basic/basic_dmd.py index 03231b857..348a403e8 100644 --- a/examples/inference/basic/basic_dmd.py +++ b/examples/inference/basic/basic_dmd.py @@ -27,6 +27,7 @@ def main(): sampling_param = SamplingParam.from_pretrained(model_name) + sampling_param.num_frames = 81 prompt = ( "A neon-lit alley in futuristic Tokyo during a heavy rainstorm at night. The puddles reflect glowing signs in kanji, advertising ramen, karaoke, and VR arcades. A woman in a translucent raincoat walks briskly with an LED umbrella. Steam rises from a street food cart, and a cat darts across the screen. Raindrops are visible on the camera lens, creating a cinematic bokeh effect." @@ -45,7 +46,7 @@ def main(): "embodying the raw energy of the wild. Low angle, steady tracking shot, " "cinematic.") start_time = time.perf_counter() - video2 = generator.generate_video(prompt2, output_path=OUTPUT_PATH, save_video=True) + video2 = generator.generate_video(prompt2, output_path=OUTPUT_PATH, save_video=True, num_frames=81) end_time = time.perf_counter() gen_time2 = end_time - start_time diff --git a/fastvideo-kernel/CMakeLists.txt b/fastvideo-kernel/CMakeLists.txt index a3af1b78f..a688e2be3 100644 --- a/fastvideo-kernel/CMakeLists.txt +++ b/fastvideo-kernel/CMakeLists.txt @@ -1,6 +1,13 @@ cmake_minimum_required(VERSION 3.26 FATAL_ERROR) project(fastvideo-kernel LANGUAGES CXX) +# Capture any caller-provided -DCMAKE_CUDA_ARCHITECTURES *before* enable_language(CUDA) +# auto-populates it with CMake's built-in default (an old arch, e.g. sm_75 on CUDA 13). +# torch's cmake actually ignores CMAKE_CUDA_ARCHITECTURES (it drives arch selection via +# TORCH_CUDA_ARCH_LIST), so we only use this captured value to honor an explicit pin by +# translating it into TORCH_CUDA_ARCH_LIST below. +set(_FASTVIDEO_USER_CUDA_ARCH "${CMAKE_CUDA_ARCHITECTURES}") + # Prefer environment variable (used by CI or uv pip install git+repo_addr) if CMake var is not explicitly set. if(NOT DEFINED GPU_BACKEND AND DEFINED ENV{GPU_BACKEND}) set(GPU_BACKEND "$ENV{GPU_BACKEND}") @@ -19,6 +26,71 @@ endif() # Find Python and Torch find_package(Python COMPONENTS Interpreter Development.Module REQUIRED) +# --------------------------------------------------------------------------- +# Resolve the target CUDA architecture, BEFORE find_package(Torch) below. +# +# torch's cmake (Caffe2 public/cuda.cmake) takes over arch selection: it emits +# the real -gencode flags from TORCH_CUDA_ARCH_LIST and forces +# CMAKE_CUDA_ARCHITECTURES to OFF. So the *effective* arch is whatever +# TORCH_CUDA_ARCH_LIST is when find_package(Torch) runs. build.sh exports it; +# standards-based builds (pip / uv pip install, sdist) don't, and torch then +# auto-detects an arch that does not match the GPU -- the kernels build but fail +# at runtime ("no kernel image is available for execution on the device"). +# Resolve it here when absent (mirrors build.sh): honor a pinned +# CMAKE_CUDA_ARCHITECTURES if given, else probe the visible GPU with torch. +# --------------------------------------------------------------------------- +if(NOT GPU_BACKEND STREQUAL "ROCM") + if(DEFINED ENV{TORCH_CUDA_ARCH_LIST}) + message(STATUS "CUDA arch: TORCH_CUDA_ARCH_LIST=$ENV{TORCH_CUDA_ARCH_LIST} (from environment)") + elseif(TORCH_CUDA_ARCH_LIST) + set(ENV{TORCH_CUDA_ARCH_LIST} "${TORCH_CUDA_ARCH_LIST}") + message(STATUS "CUDA arch: TORCH_CUDA_ARCH_LIST=${TORCH_CUDA_ARCH_LIST} (from cmake)") + else() + set(_FV_ARCH_LIST "") + if(_FASTVIDEO_USER_CUDA_ARCH) + # Caller pinned -DCMAKE_CUDA_ARCHITECTURES (which torch ignores); translate it + # to the TORCH_CUDA_ARCH_LIST spelling: "121" -> "12.1", "90a" -> "9.0a". + foreach(_fv_arch IN LISTS _FASTVIDEO_USER_CUDA_ARCH) + string(REGEX MATCH "[af]$" _fv_suffix "${_fv_arch}") + string(REGEX REPLACE "[af]$" "" _fv_num "${_fv_arch}") + string(REGEX REPLACE "(.)$" ".\\1" _fv_num "${_fv_num}") # dot before the last digit + list(APPEND _FV_ARCH_LIST "${_fv_num}${_fv_suffix}") + endforeach() + message(STATUS "CUDA arch: TORCH_CUDA_ARCH_LIST=${_FV_ARCH_LIST} (from -DCMAKE_CUDA_ARCHITECTURES=${_FASTVIDEO_USER_CUDA_ARCH})") + else() + # Best-effort probe of the visible GPU (mirrors build.sh detect_with_torch). + execute_process( + COMMAND "${Python_EXECUTABLE}" -c "import torch; assert torch.cuda.is_available(); mj, mn = torch.cuda.get_device_capability(0); print(f'{mj}.{mn}a' if (mj, mn) in ((9, 0), (12, 0)) else f'{mj}.{mn}')" + OUTPUT_VARIABLE _FV_ARCH_LIST + OUTPUT_STRIP_TRAILING_WHITESPACE + RESULT_VARIABLE _fv_detect_rc + ERROR_QUIET + ) + if(_fv_detect_rc EQUAL 0 AND _FV_ARCH_LIST) + message(STATUS "CUDA arch: TORCH_CUDA_ARCH_LIST=${_FV_ARCH_LIST} (detected via torch, live GPU)") + else() + set(_FV_ARCH_LIST "") + endif() + endif() + + if(_FV_ARCH_LIST) + set(TORCH_CUDA_ARCH_LIST "${_FV_ARCH_LIST}") + set(ENV{TORCH_CUDA_ARCH_LIST} "${_FV_ARCH_LIST}") + else() + message(FATAL_ERROR + "fastvideo-kernel: could not determine the target CUDA architecture.\n" + "Refusing to let torch auto-detect an arch that may not run on this GPU. " + "Fix with one of:\n" + " - set TORCH_CUDA_ARCH_LIST (e.g. 12.1, or 9.0a for Hopper), or\n" + " - pass -DCMAKE_CUDA_ARCHITECTURES= (e.g. 121), or\n" + " - build where the target GPU is visible to torch.\n" + "Note: 'pip/uv pip install' builds under build isolation, which hides the " + "GPU; set TORCH_CUDA_ARCH_LIST or add --no-build-isolation. " + "fastvideo-kernel/build.sh sets all of this for you.") + endif() + endif() +endif() + # Robustly find Torch include paths using Python execute_process( COMMAND "${Python_EXECUTABLE}" -c "import torch; from torch.utils.cpp_extension import include_paths; print(';'.join(include_paths()))" @@ -354,4 +426,3 @@ if(ENABLE_ATTN_QAT_INFER) install(TARGETS fp4attn_cuda LIBRARY DESTINATION .) install(TARGETS fp4quant_cuda LIBRARY DESTINATION .) endif() - diff --git a/fastvideo-kernel/build.sh b/fastvideo-kernel/build.sh index 5bc79bffb..7b7a18c36 100755 --- a/fastvideo-kernel/build.sh +++ b/fastvideo-kernel/build.sh @@ -39,8 +39,13 @@ if [[ -n "${CONDA_PREFIX:-}" ]]; then unset _need_clean _host_arch fi -# Ensure submodules are initialized if needed (tk) -git submodule update --init --recursive +# Ensure only the kernel's required headers are initialized. A repository-wide +# update also clones the unrelated VBench evaluation submodule. Skip outside a +# git checkout (e.g. Docker contexts that exclude .git), where the submodule +# contents must already be present. +if git rev-parse --git-dir >/dev/null 2>&1; then + git submodule update --init --recursive include/cutlass include/tk +fi # Install build dependencies uv pip install scikit-build-core cmake ninja diff --git a/fastvideo-kernel/csrc/turbodiffusion/gemm/gemm.cu b/fastvideo-kernel/csrc/turbodiffusion/gemm/gemm.cu index 175ad12a6..238e90d50 100644 --- a/fastvideo-kernel/csrc/turbodiffusion/gemm/gemm.cu +++ b/fastvideo-kernel/csrc/turbodiffusion/gemm/gemm.cu @@ -25,12 +25,16 @@ #include "gemm/launch.hpp" void int8_gemm( - at::Tensor const& A, at::Tensor const& A_S, - at::Tensor const& B, at::Tensor const& B_S, + at::Tensor const& A, at::Tensor const& A_S, + at::Tensor const& B, at::Tensor const& B_S, torch::Tensor& C ) { - + // The kernel dereferences raw pointers; a CPU tensor here (e.g. an Int8Linear + // never moved to CUDA) would otherwise fail as an illegal memory access. + TORCH_CHECK(A.is_cuda() && A_S.is_cuda() && B.is_cuda() && B_S.is_cuda() && C.is_cuda(), + "int8_gemm: all tensors must be on CUDA (move Int8Linear to CUDA before forward)"); + static constexpr int swizzle_dir = 1; static constexpr int swizzle_size_log = 5; diff --git a/fastvideo-kernel/python/fastvideo_kernel/block_sparse_attn_cute_fwd.py b/fastvideo-kernel/python/fastvideo_kernel/block_sparse_attn_cute_fwd.py index 6d7fa5926..c1812b988 100644 --- a/fastvideo-kernel/python/fastvideo_kernel/block_sparse_attn_cute_fwd.py +++ b/fastvideo-kernel/python/fastvideo_kernel/block_sparse_attn_cute_fwd.py @@ -182,18 +182,20 @@ def _cute_forward( block_size=(q_sparse_block_size, kv_block_size), ) + # Upstream FA4 cute (the cutlass-4.5-safe ref this repo pins) folds the + # fork's m_block_size/n_block_size pair into tile_mn, and its main path + # returns (out, lse, p, row_max) -- slice the first two. out, lse = _flash_attn_fwd( q_bshd, k_bshd, v_bshd, - m_block_size=_M_BLOCK_SIZE_DEFAULT, - n_block_size=kv_block_size, + tile_mn=(_M_BLOCK_SIZE_DEFAULT, kv_block_size), mask_mod=_build_vbs_mask_mod(kv_block_size), block_sparse_tensors=sparse_tensors, aux_tensors=[variable_block_sizes], causal=False, return_lse=True, - ) + )[:2] return out, lse diff --git a/fastvideo-kernel/tests/test_attn_qat_infer.py b/fastvideo-kernel/tests/test_attn_qat_infer.py index b553b81af..270b4d201 100644 --- a/fastvideo-kernel/tests/test_attn_qat_infer.py +++ b/fastvideo-kernel/tests/test_attn_qat_infer.py @@ -18,10 +18,15 @@ import os import sys sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..")) +import pytest import torch import torch.nn.functional as F from torch.nn.attention import SDPBackend, sdpa_kernel +# The FP4 extensions are only compiled under the sm_120a (Blackwell) arch +# gate; on other GPUs the api import below would die at collection time. +pytest.importorskip("fp4attn_cuda", reason="ATTN_QAT_INFER FP4 kernels require a sm_120a build") + from attn_qat_infer.api import sageattn_blackwell DEVICE = torch.device("cuda") diff --git a/fastvideo/attention/backends/flash_attn.py b/fastvideo/attention/backends/flash_attn.py index b389853e8..9821094fd 100644 --- a/fastvideo/attention/backends/flash_attn.py +++ b/fastvideo/attention/backends/flash_attn.py @@ -108,6 +108,8 @@ from fastvideo.attention.backends.abstract import ( from fastvideo.logger import init_logger logger = init_logger(__name__) + +_WARNED_NON_FA_DTYPE = False logger.info("Using FlashAttention-%s backend", fa_version) # FP4 FA4 support: quantize Q/K to NVFP4 E2M1 for block-scaled MMA on Blackwell. @@ -263,6 +265,32 @@ class FlashAttentionImpl(AttentionImpl): key: torch.Tensor, value: torch.Tensor, attn_metadata: FlashAttnMetadata, + ): + # FlashAttention kernels only accept fp16/bf16, but some pipelines leak + # fp32 activations into attention (observed: LTX2 video self-attn under + # SP). Cast through bf16 and restore, matching TORCH_SDPA's tolerance. + orig_dtype = query.dtype + if orig_dtype not in (torch.float16, torch.bfloat16): + global _WARNED_NON_FA_DTYPE + if not _WARNED_NON_FA_DTYPE: + _WARNED_NON_FA_DTYPE = True + logger.warning( + "FLASH_ATTN received %s inputs; casting to bfloat16 for the " + "kernel and restoring on output.", orig_dtype) + query = query.to(torch.bfloat16) + key = key.to(torch.bfloat16) + value = value.to(torch.bfloat16) + output = self._forward_impl(query, key, value, attn_metadata) + if output.dtype != orig_dtype: + output = output.to(orig_dtype) + return output + + def _forward_impl( + self, + query: torch.Tensor, + key: torch.Tensor, + value: torch.Tensor, + attn_metadata: FlashAttnMetadata, ): if (attn_metadata is not None and hasattr(attn_metadata, "attn_mask") and attn_metadata.attn_mask is not None): from fastvideo.attention.utils.flash_attn_no_pad import ( diff --git a/fastvideo/attention/utils/flash_attn_cute.py b/fastvideo/attention/utils/flash_attn_cute.py index 99cb5c73b..142ed9ccc 100644 --- a/fastvideo/attention/utils/flash_attn_cute.py +++ b/fastvideo/attention/utils/flash_attn_cute.py @@ -1,8 +1,36 @@ from __future__ import annotations +import functools +from collections.abc import Callable + import torch +from flash_attn import flash_attn_func as _flash_attn_2_func +from flash_attn import flash_attn_varlen_func as _flash_attn_2_varlen_func + +from fastvideo.logger import init_logger + +logger = init_logger(__name__) + if torch.cuda.is_available(): - from flash_attn.cute.interface import _flash_attn_bwd, _flash_attn_fwd + try: + from flash_attn.cute.interface import _flash_attn_bwd, _flash_attn_fwd + except ImportError: + # flash_attn.cute (FA4) is simply not installed -- expected on builds + # without it; callers fall back to FA3/FA2 quietly. + raise + except Exception as e: + # flash_attn.cute IS installed but failed to import -- almost always an + # nvidia-cutlass-dsl (CuTe DSL) version skew, e.g. "module + # 'cutlass.cute.core' has no attribute 'ThrMma'" (an AttributeError, not + # ImportError). This is fixable by pinning a compatible + # nvidia-cutlass-dsl, so warn loudly, then re-raise as ImportError so + # callers fall back to FA3/FA2 instead of crashing worker init. + logger.warning( + "flash_attn.cute (FA4) is installed but failed to import (%r); " + "falling back to FA3/FA2. This is usually an nvidia-cutlass-dsl " + "version mismatch -- pin a compatible nvidia-cutlass-dsl to " + "restore FA4.", e) + raise ImportError(f"flash_attn.cute (FA4) import failed: {e!r}") from e else: # This error will be caught in flash_attn.py or flash_attn_no_pad.py raise ImportError("flash_attn.cute is only available on CUDA devices; this error must be handled internally") @@ -26,6 +54,9 @@ def _flash_attn_cute_forward( causal: bool, deterministic: bool, ) -> tuple[torch.Tensor, torch.Tensor]: + # _flash_attn_fwd returns (out, lse) on its empty-sequence early path but + # (out, lse, p, row_max) on the main path at the pinned FA4 cute ref; take + # the first two so both arities work. out, lse = _flash_attn_fwd( q, k, @@ -37,7 +68,7 @@ def _flash_attn_cute_forward( softcap=0.0, num_splits=1, pack_gqa=None, - ) + )[:2] return out, lse @@ -129,7 +160,7 @@ def _flash_attn_cute_varlen_forward( softcap=0.0, num_splits=1, pack_gqa=None, - ) + )[:2] return out, lse @@ -212,6 +243,70 @@ torch.library.register_autograd( ) +# FA4's CuTeDSL kernels JIT-compile per shape family, and some configurations +# fail MLIR op creation at runtime even though the import succeeded (observed: +# GQA models on sm_89 dying in pack_gqa with "ValueError: Operation creation +# failed"). Degrade to FA2 once, process-wide, instead of crashing inference. +class _FA4Policy: + """Per-call gate for the FA4 cute fast path, with FA2 as the fallback. + + FA4 is skipped when: + * a previous call failed at runtime -- CuTeDSL JIT compilation is + shape-dependent, so the first failure disables FA4 for the rest of + the process instead of retrying a broken JIT on every call; or + * the call needs autograd -- FA4's backward asserts sm90+ (L40S/sm_89 + dies on its arch check) and is unvalidated for training in this repo + (its lse is not even allocated through our inference-shaped custom + op), so training keeps the pre-FA4 behavior: FA2 on every device. + """ + + def __init__(self) -> None: + self.broken = False + + def use_fa4(self, *tensors: torch.Tensor) -> bool: + if self.broken: + return False + return not (torch.is_grad_enabled() and any(t.requires_grad for t in tensors)) + + def mark_broken(self, error: Exception) -> None: + if not self.broken: + self.broken = True + logger.warning( + "flash_attn.cute (FA4) failed at runtime (%r); falling back " + "to FA2 for the rest of this process.", error) + + +_FA4 = _FA4Policy() + + +def _with_fa2_fallback(fa2_func: Callable) -> Callable: + """Pair an FA4 cute wrapper with its FA2 twin of the same signature. + + The decorated body runs only when ``_FA4`` allows it; otherwise (or after + the first FA4 runtime failure) the call is served by ``fa2_func``. + ``NotImplementedError`` is a contract error (e.g. dropout), not a JIT + failure, so it propagates without disabling FA4. + """ + + def decorator(fa4_func: Callable) -> Callable: + + @functools.wraps(fa4_func) + def wrapper(q: torch.Tensor, k: torch.Tensor, v: torch.Tensor, *args, **kwargs) -> torch.Tensor: + if _FA4.use_fa4(q, k, v): + try: + return fa4_func(q, k, v, *args, **kwargs) + except NotImplementedError: + raise + except Exception as e: # CuTeDSL compile errors surface as ValueError + _FA4.mark_broken(e) + return fa2_func(q, k, v, *args, **kwargs) + + return wrapper + + return decorator + + +@_with_fa2_fallback(_flash_attn_2_func) def flash_attn_func( q: torch.Tensor, k: torch.Tensor, @@ -250,7 +345,7 @@ def _flash_attn_cute_fp4_forward( softmax_scale: float | None, causal: bool, ) -> torch.Tensor: - out, _ = _flash_attn_fwd( + out = _flash_attn_fwd( q, k, v, @@ -263,7 +358,7 @@ def _flash_attn_cute_fp4_forward( pack_gqa=None, mSFQ=sfq, mSFK=sfk, - ) + )[0] return out @@ -297,6 +392,7 @@ def flash_attn_fp4_func( return torch.ops.fastvideo._flash_attn_cute_fp4_forward(q, k, v, sfq, sfk, softmax_scale, causal) +@_with_fa2_fallback(_flash_attn_2_varlen_func) def flash_attn_varlen_func( q: torch.Tensor, k: torch.Tensor, diff --git a/fastvideo/eval/README.md b/fastvideo/eval/README.md index 94b48f1a1..574e0f91f 100644 --- a/fastvideo/eval/README.md +++ b/fastvideo/eval/README.md @@ -19,10 +19,7 @@ absolute VLM scorer (`videoscore2`), and a pairwise VLM judge `[eval-audio]` covers every `audio.*` metric. ImageBind (`facebookresearch/ImageBind`, CC BY-NC-SA 4.0) is git-sourced via -`[tool.uv.sources]` rather than vendored. `torchaudio` at the cu128 -wheel is pulled transitively by `audiobox_aesthetics`; on cu128 hosts -using raw `pip`, install `torchaudio` from -`https://download.pytorch.org/whl/cu128` first. +`[tool.uv.sources]` rather than vendored. `[eval-fast-decode]` is opt-in. It pulls `decord`, which is faster than the default PyAV decoder but has no aarch64 wheels and is effectively diff --git a/fastvideo/eval/metrics/audio/clap_score/metric.py b/fastvideo/eval/metrics/audio/clap_score/metric.py index 132e4ea2d..ee8c9e346 100644 --- a/fastvideo/eval/metrics/audio/clap_score/metric.py +++ b/fastvideo/eval/metrics/audio/clap_score/metric.py @@ -62,16 +62,24 @@ class ClapScoreMetric(BaseMetric): audio, _ = librosa.load(audio_path, sr=CLAP_SAMPLE_RATE, mono=True) return pyln.normalize.peak(audio, -1.0) + @staticmethod + def _projected_features(features) -> torch.Tensor: + # transformers >= 5 returns a ModelOutput whose pooler_output holds the + # (L2-normalized) projected CLAP features; 4.x returned the projected + # tensor directly. Cosine similarity is scale-invariant, so both yield + # the same score. + return features.pooler_output if hasattr(features, "pooler_output") else features + def _audio_emb(self, audio_path: str) -> torch.Tensor: audio = self._load_audio(audio_path) inputs = self._processor(audio=audio, sampling_rate=CLAP_SAMPLE_RATE, return_tensors="pt") inputs = {k: v.to(self.device) for k, v in inputs.items()} - return self._model.get_audio_features(**inputs) + return self._projected_features(self._model.get_audio_features(**inputs)) def _text_emb(self, text: str) -> torch.Tensor: inputs = self._processor(text=[text], return_tensors="pt", padding=True) inputs = {k: v.to(self.device) for k, v in inputs.items()} - return self._model.get_text_features(**inputs) + return self._projected_features(self._model.get_text_features(**inputs)) @torch.no_grad() def compute(self, sample: dict) -> MetricResult: diff --git a/fastvideo/models/dits/ltx2.py b/fastvideo/models/dits/ltx2.py index fb197bec2..bdfeef0c7 100644 --- a/fastvideo/models/dits/ltx2.py +++ b/fastvideo/models/dits/ltx2.py @@ -93,7 +93,12 @@ class StageAwareRMSNorm(nn.RMSNorm): weight=self.weight, eps=self.eps, ) - return super().forward(x) + # torch 2.12 added rms_norm to autocast's float32 cast policy + # (aten/src/ATen/autocast_mode.h), so under autocast(bfloat16) this + # norm now returns float32 where torch <= 2.11 returned bfloat16. + # Restore the input dtype so autocast-blind consumers downstream + # (e.g. the flash-attention custom op) don't receive upcast q/k. + return super().forward(x).to(x.dtype) def _supports_prequantized_input(linear: ReplicatedLinear) -> bool: diff --git a/fastvideo/tests/modal/launch_l40s_job.py b/fastvideo/tests/modal/launch_l40s_job.py index 8a7f36a06..bad9bf0db 100644 --- a/fastvideo/tests/modal/launch_l40s_job.py +++ b/fastvideo/tests/modal/launch_l40s_job.py @@ -30,6 +30,16 @@ from typing import Any import modal +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +try: + from modal_image_utils import resolve_image_ref # noqa: E402 +except ModuleNotFoundError: + # Remote Modal containers re-import this module but mount only the + # entrypoint file; the digest resolution already happened at local + # launch time, so a passthrough is correct there. + def resolve_image_ref(image_ref: str) -> str: + return image_ref + app = modal.App("fastvideo-gpu-job") REPO_DIR = "/FastVideo" @@ -39,6 +49,7 @@ IMAGE_TAG = os.environ.get( "FASTVIDEO_MODAL_IMAGE", f"ghcr.io/hao-ai-lab/fastvideo/fastvideo-dev:{IMAGE_VERSION}", ) +IMAGE_REF = resolve_image_ref(IMAGE_TAG) SECRET_ENV_KEYS = ( "HF_API_KEY", "HUGGINGFACE_HUB_TOKEN", @@ -48,7 +59,7 @@ SECRET_ENV_KEYS = ( "WANDB_MODE", ) -print(f"Using image: {IMAGE_TAG}") +print(f"Using image: {IMAGE_REF}") print(f"Using Modal volume: {MODEL_VOLUME_NAME}") model_vol = modal.Volume.from_name(MODEL_VOLUME_NAME, create_if_missing=True) @@ -58,8 +69,18 @@ local_secrets = modal.Secret.from_dict({ if os.environ.get(key) }) +# Mutable tags inherit the registry image's baked backend, including custom +# FASTVIDEO_MODAL_IMAGE overrides. Explicit CUDA tags also work with older +# images that predate the baked setting, and a caller override always wins. +uv_torch_backend_override = os.environ.get("UV_TORCH_BACKEND") +if not uv_torch_backend_override: + if "cuda13" in IMAGE_TAG.lower(): + uv_torch_backend_override = "cu130" + elif "cuda12.6" in IMAGE_TAG.lower(): + uv_torch_backend_override = "cu126" + image = ( - modal.Image.from_registry(IMAGE_TAG, add_python="3.12") + modal.Image.from_registry(IMAGE_REF, add_python="3.12") .apt_install( "cmake", "pkg-config", @@ -75,6 +96,7 @@ image = ( "PATH": "/root/.cargo/bin:$PATH", "HF_HOME": "/root/data/.cache", "TOKENIZERS_PARALLELISM": "false", + **({"UV_TORCH_BACKEND": uv_torch_backend_override} if uv_torch_backend_override else {}), "FASTVIDEO_ATTENTION_BACKEND": os.environ.get("FASTVIDEO_ATTENTION_BACKEND", "FLASH_ATTN"), }) ) diff --git a/fastvideo/tests/modal/modal_image_utils.py b/fastvideo/tests/modal/modal_image_utils.py new file mode 100644 index 000000000..2ec1d2fcd --- /dev/null +++ b/fastvideo/tests/modal/modal_image_utils.py @@ -0,0 +1,57 @@ +"""Shared helpers for the Modal launcher scripts in this directory. + +Standalone module: the launchers import it by file path (sys.path trick), not +through the ``fastvideo`` package, so they keep working on thin CI hosts that +have ``modal`` but not torch. +""" +import json +import urllib.request + +_REGISTRY = "ghcr.io" +_ACCEPT_MANIFEST_TYPES = ", ".join([ + "application/vnd.oci.image.index.v1+json", + "application/vnd.docker.distribution.manifest.list.v2+json", + "application/vnd.oci.image.manifest.v1+json", + "application/vnd.docker.distribution.manifest.v2+json", +]) + + +def resolve_image_ref(image_ref: str) -> str: + """Pin a mutable ghcr.io tag to its current digest (``tag@sha256:...``). + + Modal caches registry images by reference string and never re-pulls a + moved tag, so a mutable tag like ``py3.12-latest`` silently serves stale + bytes after a new image is published. Resolving the tag to its digest at + launch time makes Modal's cache key track the actual image: a new publish + changes the digest (fresh pull), an unchanged image keeps the cache hit. + + Falls back to the unresolved tag, with a warning, if the registry cannot + be reached (e.g. offline) — that preserves today's behavior. + """ + if "@" in image_ref or not image_ref.startswith(f"{_REGISTRY}/"): + return image_ref + repo_path, _, tag = image_ref[len(_REGISTRY) + 1:].partition(":") + if not tag: + return image_ref + try: + token_url = f"https://{_REGISTRY}/token?scope=repository:{repo_path}:pull" + with urllib.request.urlopen(token_url, timeout=10) as response: + token = json.load(response)["token"] + manifest_request = urllib.request.Request( + f"https://{_REGISTRY}/v2/{repo_path}/manifests/{tag}", + method="HEAD", + headers={ + "Authorization": f"Bearer {token}", + "Accept": _ACCEPT_MANIFEST_TYPES, + }, + ) + with urllib.request.urlopen(manifest_request, timeout=10) as response: + digest = response.headers.get("Docker-Content-Digest", "") + if digest.startswith("sha256:"): + return f"{image_ref}@{digest}" + print(f"WARNING: registry returned no digest for {image_ref}; " + "Modal may reuse a stale cached image for this tag.") + except Exception as error: # noqa: BLE001 - never fail the launch over this + print(f"WARNING: could not resolve {image_ref} to a digest ({error}); " + "Modal may reuse a stale cached image for this tag.") + return image_ref diff --git a/fastvideo/tests/modal/pr_test.py b/fastvideo/tests/modal/pr_test.py index 15edb8fc4..5bafde897 100644 --- a/fastvideo/tests/modal/pr_test.py +++ b/fastvideo/tests/modal/pr_test.py @@ -1,16 +1,38 @@ import os +import sys import modal +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +try: + from modal_image_utils import resolve_image_ref # noqa: E402 +except ModuleNotFoundError: + # Remote Modal containers re-import this module but mount only the + # entrypoint file; the digest resolution already happened at local + # launch time, so a passthrough is correct there. + def resolve_image_ref(image_ref: str) -> str: + return image_ref + app = modal.App() model_vol = modal.Volume.from_name("hf-model-weights") -image_version = os.getenv("IMAGE_VERSION") +image_version = os.getenv("IMAGE_VERSION", "latest") image_tag = f"ghcr.io/hao-ai-lab/fastvideo/fastvideo-dev:{image_version}" -print(f"Using image: {image_tag}") +image_ref = resolve_image_ref(image_tag) +print(f"Using image: {image_ref}") + +# Mutable tags inherit the registry image's baked backend, keeping a latest-tag +# transition safe. Explicit CUDA tags also work with older images that predate +# the baked setting, and a caller override always wins. +uv_torch_backend_override = os.environ.get("UV_TORCH_BACKEND") +if not uv_torch_backend_override: + if "cuda13" in image_tag.lower(): + uv_torch_backend_override = "cu130" + elif "cuda12.6" in image_tag.lower(): + uv_torch_backend_override = "cu126" image = (modal.Image.from_registry( - image_tag, add_python="3.12" + image_ref, add_python="3.12" ).run_commands("rm -rf /FastVideo").apt_install( "cmake", "pkg-config", "build-essential", "curl", "libssl-dev", "ffmpeg" ).run_commands( @@ -35,7 +57,10 @@ image = (modal.Image.from_registry( "TEST_SCOPE": os.environ.get("TEST_SCOPE", ""), "IMAGE_VERSION": - os.environ.get("IMAGE_VERSION", ""), + image_version, + **({ + "UV_TORCH_BACKEND": uv_torch_backend_override + } if uv_torch_backend_override else {}), "HF_REPO_ID": "FastVideo/performance-tracking", })) @@ -47,16 +72,23 @@ dreamverse_image = (image.run_commands( def run_test(pytest_command: str): """Helper function to run a test suite with custom pytest command""" - run_test_command(f'uv pip install -e ".[test]" && {pytest_command}', - build_kernel=True) + run_test_command(pytest_command, build_kernel=True) -def run_test_command(test_command: str, build_kernel: bool): +def run_test_command(test_command: str, + build_kernel: bool, + install_command: str = 'uv pip install -e ".[test]"'): """Helper function to run a test suite with custom test command. Most FastVideo CI suites need the custom kernel build. App-level tests like DreamVerse's mock-backend UI checks do not, so keep the kernel build optional to avoid unrelated CUDA/kernel setup in that CI path. + + The dependency install runs BEFORE the kernel build: pyproject pins the + PyPI fastvideo-kernel wheel, so an install after the build silently + replaces the just-built in-tree kernel with the (older) wheel -- every + lane would then test stale kernels. Pass install_command="" for commands + that manage their own installs. """ import subprocess import sys @@ -85,6 +117,8 @@ def run_test_command(test_command: str, build_kernel: bool): cd .. && """ if build_kernel else "" + install_clause = f"{install_command} &&" if install_command else "" + command = f""" source $HOME/.local/bin/env && source /opt/venv/bin/activate && @@ -92,6 +126,7 @@ def run_test_command(test_command: str, build_kernel: bool): cd /FastVideo && {checkout_command} && git submodule update --init --recursive && + {install_clause} {build_kernel_command} {test_command} """ @@ -246,7 +281,9 @@ def run_unit_test(): @app.function(gpu="L40S:1", image=dreamverse_image, timeout=1800) def run_dreamverse_app_tests(): run_test_command( - """ + install_command="", + build_kernel=False, + test_command=""" uv pip install -e ".[test,dreamverse]" && export PYTHONPATH=/FastVideo/apps/dreamverse:$PYTHONPATH && pytest apps/dreamverse/dreamverse/tests -q && @@ -274,8 +311,7 @@ def run_dreamverse_app_tests(): --project=mobile-safari \ --project=mobile-chromium ' - """, - build_kernel=False) + """) @app.function(gpu="L40S:1", @@ -342,9 +378,9 @@ def run_eval_tests(): # pass vacuously. detectron2-backed vbench metrics remain skipped by # design (not pip-installable; see fastvideo/eval/README.md). run_test_command( - 'uv pip install -e ".[test,eval-full]" && ' "export HF_HOME='/root/data/.cache' && hf auth login --token $HF_API_KEY && pytest ./fastvideo/tests/eval -vs", - build_kernel=True) + build_kernel=True, + install_command='uv pip install -e ".[test,eval-full]"') @app.function(gpu="L40S:1", diff --git a/fastvideo/tests/modal/ssim_test.py b/fastvideo/tests/modal/ssim_test.py index 106aa304a..1e0611725 100644 --- a/fastvideo/tests/modal/ssim_test.py +++ b/fastvideo/tests/modal/ssim_test.py @@ -11,15 +11,36 @@ from typing import Any import modal +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +try: + from modal_image_utils import resolve_image_ref # noqa: E402 +except ModuleNotFoundError: + # Remote Modal containers re-import this module but mount only the + # entrypoint file; the digest resolution already happened at local + # launch time, so a passthrough is correct there. + def resolve_image_ref(image_ref: str) -> str: + return image_ref + app = modal.App() model_vol = modal.Volume.from_name("hf-model-weights") image_version = os.getenv("IMAGE_VERSION", "latest") image_tag = f"ghcr.io/hao-ai-lab/fastvideo/fastvideo-dev:{image_version}" -print(f"Using image: {image_tag}") +image_ref = resolve_image_ref(image_tag) +print(f"Using image: {image_ref}") + +# Mutable tags inherit the registry image's baked backend, keeping a latest-tag +# transition safe. Explicit CUDA tags also work with older images that predate +# the baked setting, and a caller override always wins. +uv_torch_backend_override = os.environ.get("UV_TORCH_BACKEND") +if not uv_torch_backend_override: + if "cuda13" in image_tag.lower(): + uv_torch_backend_override = "cu130" + elif "cuda12.6" in image_tag.lower(): + uv_torch_backend_override = "cu126" image = ( - modal.Image.from_registry(image_tag, add_python="3.12") + modal.Image.from_registry(image_ref, add_python="3.12") .apt_install( "cmake", "pkg-config", @@ -41,7 +62,8 @@ image = ( "BUILDKITE_REPO": os.environ.get("BUILDKITE_REPO", ""), "BUILDKITE_COMMIT": os.environ.get("BUILDKITE_COMMIT", ""), "BUILDKITE_PULL_REQUEST": os.environ.get("BUILDKITE_PULL_REQUEST", ""), - "IMAGE_VERSION": os.environ.get("IMAGE_VERSION", ""), + "IMAGE_VERSION": image_version, + **({"UV_TORCH_BACKEND": uv_torch_backend_override} if uv_torch_backend_override else {}), } ) ) diff --git a/fastvideo/third_party/eval/synchformer/hf_src/modeling_ast.py b/fastvideo/third_party/eval/synchformer/hf_src/modeling_ast.py index ff9faccf9..19ec981bf 100644 --- a/fastvideo/third_party/eval/synchformer/hf_src/modeling_ast.py +++ b/fastvideo/third_party/eval/synchformer/hf_src/modeling_ast.py @@ -527,7 +527,12 @@ class ASTModel(ASTPreTrainedModel): # attention_probs has shape bsz x n_heads x N x N # input head_mask has shape [num_heads] or [num_hidden_layers x num_heads] # and head_mask is converted to shape [num_hidden_layers x batch x num_heads x seq_length x seq_length] - head_mask = self.get_head_mask(head_mask, self.config.num_hidden_layers) + # transformers 5.x removed PreTrainedModel.get_head_mask; replicate the + # old helper for the None case (synchformer never passes a head mask). + if head_mask is None: + head_mask = [None] * self.config.num_hidden_layers + else: + head_mask = self.get_head_mask(head_mask, self.config.num_hidden_layers) embedding_output = self.embeddings(input_values) diff --git a/pyproject.toml b/pyproject.toml index feb502cdb..ad7784b16 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -22,14 +22,18 @@ dependencies = [ # Machine Learning & Transformers "transformers>=4.57.3", - "tokenizers>=0.20.1", + # <0.23: tokenizers 0.23 renamed RobertaProcessing's binding args, so + # transformers' CLIP-style tokenizer loading dies with + # "RobertaProcessing.__new__() got an unexpected keyword argument 'cls'". + "tokenizers>=0.20.1,<0.23", "sentencepiece>=0.2.0", "timm>=1.0.11", "peft>=0.15.0", "diffusers>=0.38.0", - "torch==2.11.0", + "torch==2.12.0", "torchvision", "torchaudio", + "flashinfer-python", # Acceleration & Optimization "accelerate==1.0.1", @@ -93,30 +97,25 @@ prerelease = "allow" members = ["apps/dreamverse"] [tool.uv.sources] -torch = [ - { index = "pytorch-cpu", marker = "sys_platform != 'linux'" }, - { index = "pytorch-cu128", marker = "sys_platform == 'linux'" }, -] -torchvision = [ - { index = "pytorch-cpu", marker = "sys_platform != 'linux'" }, - { index = "pytorch-cu128", marker = "sys_platform == 'linux'" }, -] -torchaudio = [ - { index = "pytorch-cpu", marker = "sys_platform != 'linux'" }, - { index = "pytorch-cu128", marker = "sys_platform == 'linux'" }, +# No prebuilt aarch64 wheel for fastvideo-kernel exists on PyPI, so on ARM +# (e.g. the NVIDIA DGX Spark / GB10) build it from the in-tree source instead; +# x86_64 keeps the published PyPI wheel. This table is uv-only: plain pip ignores +# it and it is not part of the published wheel metadata, so PyPI consumers are +# unaffected. Pass --no-sources to bypass it (e.g. when the kernel is already +# built/installed separately, as in the Spark install guide). +fastvideo-kernel = [ + { path = "fastvideo-kernel", marker = "platform_machine == 'aarch64'" }, ] +# torch / torchvision / torchaudio are intentionally NOT pinned to a PyTorch +# index here. Pick the supported build explicitly at install time: +# UV_TORCH_BACKEND=cu126 uv pip install -e ".[dev]" # CUDA 12 +# UV_TORCH_BACKEND=cu130 uv pip install -e ".[dev]" # CUDA 13 +# uv reads UV_TORCH_BACKEND / --torch-backend (not a pyproject setting). The +# supported backends (cu126, cu130) both provide torch 2.12.0. imagebind = { git = "https://github.com/facebookresearch/ImageBind.git", rev = "53680b02d7e37b19b124fa37bae4b6c98c38f5be" } -flash-attn-cute = { git = "https://github.com/XOR-op/flash-attention.git", branch = "fa4-compile", subdirectory = "flash_attn/cute" } - -[[tool.uv.index]] -name = "pytorch-cpu" -url = "https://download.pytorch.org/whl/cpu" -explicit = true - -[[tool.uv.index]] -name = "pytorch-cu128" -url = "https://download.pytorch.org/whl/cu128" -explicit = true +# FA4 cute: upstream Dao-AILab (cutlass-4.5-safe; uses cute.ThrMma). torch.compile +# support comes from FastVideo's own custom_op wrappers, so no fork is needed. +flash-attn-4 = { git = "https://github.com/Dao-AILab/flash-attention.git", rev = "940cd9680f3315f2f06b43ab5bea2c2cf2d96806", subdirectory = "flash_attn/cute" } [project.optional-dependencies] @@ -151,7 +150,9 @@ eval-physics-iq = [] eval-audio = ["jiwer", "librosa", "pyloudnorm", "hear21passt", "audiobox_aesthetics", "imagebind", "pytorchvideo"] eval-judge = ["google-genai"] eval = [ - "lpips", "ptlflow", "qwen-vl-utils", + # ptlflow>=0.4: get_model() takes ckpt_path (0.3 called it + # pretrained_ckpt); unpinned, the resolver backtracked to 0.2.5. + "lpips", "ptlflow>=0.4", "qwen-vl-utils", "fastvideo[eval-vbench]", "fastvideo[eval-physics-iq]", ] eval-fast-decode = ["decord"] @@ -176,10 +177,10 @@ streaming = [ dreamverse = [ "uvicorn[standard]>=0.41.0", "cerebras-cloud-sdk", - # PyPI forbids direct URL deps in published metadata; pin the fork via + # PyPI forbids direct URL deps in published metadata; pin upstream cute via # [tool.uv.sources] above (same pattern as imagebind) so the wheel stays - # publishable while uv workspace installs still get the fork. - "flash-attn-cute", + # publishable while uv workspace installs still get it. + "flash-attn-4", "flashinfer-python", "openai>=1.40", ]