# 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=14c377950125c70b7a9dabf9c561fca53715ac7d

# 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 \
    ffmpeg \
    libgl1 \
    libglib2.0-0 \
    libx11-dev \
    gcc-11 \
    g++-11 \
    clang-11 \
    cmake \
    pkg-config \
    build-essential \
    libssl-dev \
    && rm -rf /var/lib/apt/lists/*

# Rust toolchain: some dependencies only ship sdists on aarch64 and need cargo
# to build. The dormant legacy Modal image layers the identical apt set +
# rustup on top of this image (fastvideo/tests/modal/pr_test.py); baking both
# here keeps its manual rollback path reproducible without changing the Slurm
# runner's package surface.
RUN set -o pipefail && \
    curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain stable --profile minimal && \
    /root/.cargo/bin/cargo --version && /root/.cargo/bin/rustc --version
ENV PATH=/root/.cargo/bin:${PATH}

# 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]" && \
    python -c "import cv2; print('OpenCV', cv2.__version__)" && \
    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 CuTe-DSL-4.6-compatible 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. The pinned stack is validated on ARM64 GB200 (sm_100) as well
# as x86; FA4 remains opt-in through FASTVIDEO_FA4=1 so lanes with FA2 baselines
# keep their existing numerics.
RUN --mount=type=cache,target=/opt/uv/cache \
    source $HOME/.local/bin/env && \
    source /opt/venv/bin/activate && \
    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')"

COPY . .

# Build immutable FastVideo kernel wheels for the published image. The requested
# architecture remains installed for normal image users; amd64 images also carry
# an SM89 artifact for L40S users and the dormant legacy rollback path.
ARG FASTVIDEO_KERNEL_PREBUILT_DIR=/opt/fastvideo-kernel-prebuilt
RUN --mount=type=cache,target=/opt/uv/cache \
    source $HOME/.local/bin/env && \
    source /opt/venv/bin/activate && \
    default_arch="${TORCH_CUDA_ARCH_LIST}" && \
    default_wheel_dir="${FASTVIDEO_KERNEL_PREBUILT_DIR}/${default_arch}" && \
    export TORCH_CUDA_ARCH_LIST="${default_arch}" && \
    cd fastvideo-kernel && \
    CMAKE_ARGS= CMAKE_BUILD_PARALLEL_LEVEL=${CMAKE_BUILD_PARALLEL_LEVEL} \
    ./build.sh --wheel-dir "${default_wheel_dir}" && \
    cd /FastVideo && \
    CMAKE_ARGS= python fastvideo/tests/modal/kernel_build_cache.py write-build-info \
        --wheel-dir "${default_wheel_dir}" \
        --output "${default_wheel_dir}/metadata.json" && \
    if [[ "${TARGETARCH:-amd64}" == "amd64" && "${default_arch}" != "8.9" ]]; then \
        export TORCH_CUDA_ARCH_LIST=8.9 && \
        l40s_wheel_dir="${FASTVIDEO_KERNEL_PREBUILT_DIR}/8.9" && \
        cd /FastVideo/fastvideo-kernel && \
        CMAKE_ARGS= CMAKE_BUILD_PARALLEL_LEVEL=${CMAKE_BUILD_PARALLEL_LEVEL} \
        ./build.sh --wheel-dir "${l40s_wheel_dir}" && \
        cd /FastVideo && \
        CMAKE_ARGS= python fastvideo/tests/modal/kernel_build_cache.py write-build-info \
            --wheel-dir "${l40s_wheel_dir}" \
            --output "${l40s_wheel_dir}/metadata.json"; \
    fi && \
    export TORCH_CUDA_ARCH_LIST="${default_arch}" && \
    default_wheel="$(find "${default_wheel_dir}" -maxdepth 1 -type f \
        \( -name 'fastvideo_kernel-*.whl' -o -name 'fastvideo-kernel-*.whl' \) \
        | sort | tail -n 1)" && \
    uv pip install "${default_wheel}" \
        --reinstall-package fastvideo-kernel --no-deps

# 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
