From 98be9b3da2cabaf3cdc2871e5a244a94ab8d4fd1 Mon Sep 17 00:00:00 2001 From: William Lin Date: Sun, 5 Jul 2026 06:43:27 -0700 Subject: [PATCH] [bugfix]: address the three remaining #1447 review findings (#1549) --- fastvideo-kernel/CMakeLists.txt | 9 +++++++++ fastvideo-kernel/build.sh | 17 +++++++++++++++++ fastvideo/tests/modal/launch_l40s_job.py | 13 ++++++------- fastvideo/tests/modal/modal_image_utils.py | 21 +++++++++++++++++++++ fastvideo/tests/modal/pr_test.py | 13 ++++++------- fastvideo/tests/modal/ssim_test.py | 13 ++++++------- 6 files changed, 65 insertions(+), 21 deletions(-) diff --git a/fastvideo-kernel/CMakeLists.txt b/fastvideo-kernel/CMakeLists.txt index ee77d8cb1..79fdf84b3 100644 --- a/fastvideo-kernel/CMakeLists.txt +++ b/fastvideo-kernel/CMakeLists.txt @@ -53,7 +53,16 @@ if(NOT GPU_BACKEND STREQUAL "ROCM") 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". + # Only numeric spellings translate; keywords like "native"/"all" would + # otherwise be mangled into nonsense ("nativ.e"). foreach(_fv_arch IN LISTS _FASTVIDEO_USER_CUDA_ARCH) + if(NOT _fv_arch MATCHES "^[0-9]+[af]?$") + message(FATAL_ERROR + "fastvideo-kernel: CMAKE_CUDA_ARCHITECTURES='${_fv_arch}' is not " + "supported. Use a numeric arch (e.g. 90a, 121), set " + "TORCH_CUDA_ARCH_LIST directly (e.g. 9.0a), or unset both to " + "auto-detect from the visible GPU.") + endif() 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 diff --git a/fastvideo-kernel/build.sh b/fastvideo-kernel/build.sh index 7b7a18c36..602000b26 100755 --- a/fastvideo-kernel/build.sh +++ b/fastvideo-kernel/build.sh @@ -46,6 +46,23 @@ fi if git rev-parse --git-dir >/dev/null 2>&1; then git submodule update --init --recursive include/cutlass include/tk fi +# Fail fast with a clear message if the headers are still missing (e.g. a +# Docker context that excluded .git AND the submodule contents) instead of +# dying later in a wall of nvcc include errors. CUTLASS is consumed by the +# always-built turbodiffusion sources, so it is a hard error; ThunderKittens +# only feeds the TK-gated Hopper kernels, so a missing tree just warns (the +# TK gate resolves later, and non-SM90/ROCm builds never touch it). +if [ ! -d include/cutlass/include ]; then + echo "ERROR: include/cutlass/include is missing. Outside a git checkout the" >&2 + echo " CUTLASS sources must already be present (run" >&2 + echo " 'git submodule update --init --recursive include/cutlass include/tk'" >&2 + echo " in the source checkout, or include them in the build context)." >&2 + exit 1 +fi +if [ ! -d include/tk/include ]; then + echo "WARNING: include/tk/include is missing; ThunderKittens (Hopper sm_90a)" >&2 + echo " kernels cannot be built. Fine for non-SM90/ROCm targets." >&2 +fi # Install build dependencies uv pip install scikit-build-core cmake ninja diff --git a/fastvideo/tests/modal/launch_l40s_job.py b/fastvideo/tests/modal/launch_l40s_job.py index 33e1ed07b..d951633b9 100644 --- a/fastvideo/tests/modal/launch_l40s_job.py +++ b/fastvideo/tests/modal/launch_l40s_job.py @@ -32,7 +32,8 @@ import modal sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) try: - from modal_image_utils import resolve_image_ref # noqa: E402 + from modal_image_utils import ( # noqa: E402 + resolve_image_ref, resolve_uv_torch_backend) except ModuleNotFoundError: # Remote Modal containers re-import this module but mount only the # entrypoint file; the digest resolution already happened at local @@ -40,6 +41,9 @@ except ModuleNotFoundError: def resolve_image_ref(image_ref: str) -> str: return image_ref + def resolve_uv_torch_backend(image_tag: str) -> str | None: + return os.environ.get("UV_TORCH_BACKEND") + app = modal.App("fastvideo-gpu-job") REPO_DIR = "/FastVideo" @@ -72,12 +76,7 @@ local_secrets = modal.Secret.from_dict({ # 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" +uv_torch_backend_override = resolve_uv_torch_backend(IMAGE_TAG) image = ( modal.Image.from_registry(IMAGE_REF, add_python="3.12") diff --git a/fastvideo/tests/modal/modal_image_utils.py b/fastvideo/tests/modal/modal_image_utils.py index 2ec1d2fcd..88a772901 100644 --- a/fastvideo/tests/modal/modal_image_utils.py +++ b/fastvideo/tests/modal/modal_image_utils.py @@ -5,6 +5,7 @@ through the ``fastvideo`` package, so they keep working on thin CI hosts that have ``modal`` but not torch. """ import json +import os import urllib.request _REGISTRY = "ghcr.io" @@ -55,3 +56,23 @@ def resolve_image_ref(image_ref: str) -> str: 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 + + +def resolve_uv_torch_backend(image_tag: str) -> str | None: + """UV_TORCH_BACKEND for a launcher image tag. + + A caller-set UV_TORCH_BACKEND always wins. Otherwise sniff the CUDA + version from an explicit image tag (cuda13 -> cu130, cuda12.6 -> cu126) + so uv resolves torch against the image's toolkit. Mutable tags (e.g. + py3.12-latest) return None and inherit the registry image's baked + backend, which keeps a latest-tag CUDA transition safe. + """ + override = os.environ.get("UV_TORCH_BACKEND") + if override: + return override + tag = image_tag.lower() + if "cuda13" in tag: + return "cu130" + if "cuda12.6" in tag: + return "cu126" + return None diff --git a/fastvideo/tests/modal/pr_test.py b/fastvideo/tests/modal/pr_test.py index 8e9cd9aa9..acf10e65a 100644 --- a/fastvideo/tests/modal/pr_test.py +++ b/fastvideo/tests/modal/pr_test.py @@ -5,7 +5,8 @@ import modal sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) try: - from modal_image_utils import resolve_image_ref # noqa: E402 + from modal_image_utils import ( # noqa: E402 + resolve_image_ref, resolve_uv_torch_backend) except ModuleNotFoundError: # Remote Modal containers re-import this module but mount only the # entrypoint file; the digest resolution already happened at local @@ -13,6 +14,9 @@ except ModuleNotFoundError: def resolve_image_ref(image_ref: str) -> str: return image_ref + def resolve_uv_torch_backend(image_tag: str) -> str | None: + return os.environ.get("UV_TORCH_BACKEND") + app = modal.App() model_vol = modal.Volume.from_name("hf-model-weights") @@ -24,12 +28,7 @@ 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" +uv_torch_backend_override = resolve_uv_torch_backend(image_tag) image = (modal.Image.from_registry( image_ref, add_python="3.12" diff --git a/fastvideo/tests/modal/ssim_test.py b/fastvideo/tests/modal/ssim_test.py index e7e2b84b7..21c4b9850 100644 --- a/fastvideo/tests/modal/ssim_test.py +++ b/fastvideo/tests/modal/ssim_test.py @@ -13,7 +13,8 @@ import modal sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) try: - from modal_image_utils import resolve_image_ref # noqa: E402 + from modal_image_utils import ( # noqa: E402 + resolve_image_ref, resolve_uv_torch_backend) except ModuleNotFoundError: # Remote Modal containers re-import this module but mount only the # entrypoint file; the digest resolution already happened at local @@ -21,6 +22,9 @@ except ModuleNotFoundError: def resolve_image_ref(image_ref: str) -> str: return image_ref + def resolve_uv_torch_backend(image_tag: str) -> str | None: + return os.environ.get("UV_TORCH_BACKEND") + app = modal.App() model_vol = modal.Volume.from_name("hf-model-weights") @@ -32,12 +36,7 @@ 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" +uv_torch_backend_override = resolve_uv_torch_backend(image_tag) image = ( modal.Image.from_registry(image_ref, add_python="3.12")