# DeepSeek-V4.1-Flash — DGX Spark (arm64, sm_121a), clone-at-pin.
#
# One Dockerfile for tonyd2wild's overlay1/3/5 chain (MIT; see versions.env
# header + patches/README note). No fallbacks: every gate fails loud.
#
#   base    pinned vllm/vllm-openai nightly (arm64 digest)
#   vLLM    vllm-project/vllm @ dsv41-feat commit+tree (day-0 DSV4.1 support)
#   ext     _C_stable_libtorch rebuilt for sm_121a (stable-only cmake build)
#   FI      FlashInfer 0.7.0rc1 from pinned source (0.6.18 lacks the SM120
#           sparse-MLA decode kernel for page_block_size=32)
#   warm    sparse-MLA + mxfp8 JIT kernels prebuilt in the image runtime env
#   patches the 7 boot-10 file replacements, baked over dist-packages,
#           md5-gated against the published manifest
#
# Model: deepseek-ai/DeepSeek-V4.1-Flash (510 GB; Engram tables stay on disk
# via the baked engram.py patch — DSV41_ENGRAM_DISK=1 at serve time).

ARG VLLM_BASE_IMAGE
FROM ${VLLM_BASE_IMAGE}

ARG VLLM_REPO
ARG VLLM_REF
ARG VLLM_REF_TREE
ARG VLLM_PRETEND_VERSION
ARG CUTLASS_TAG
ARG CUTLASS_URL
ARG FI_SHA
ARG FI_CUTLASS_SHA
ARG FI_CCCL_SHA
ARG FI_SPDLOG_SHA

ENV DEBIAN_FRONTEND=noninteractive \
    PIP_DISABLE_PIP_VERSION_CHECK=1 \
    MAX_JOBS=4

# ---- vLLM python tree @ pin (clone-at-pin: commit + tree hash gates) ----
RUN set -eux; \
    apt-get update -qq && apt-get install -y --no-install-recommends git ca-certificates >/dev/null && rm -rf /var/lib/apt/lists/*; \
    git config --global protocol.version 0; \
    git init -q /src/vllm; \
    git -C /src/vllm fetch --depth 1 ${VLLM_REPO} ${VLLM_REF}; \
    git -C /src/vllm checkout -q ${VLLM_REF}; \
    test "$(git -C /src/vllm rev-parse HEAD)" = "${VLLM_REF}"; \
    test "$(git -C /src/vllm rev-parse 'HEAD^{tree}')" = "${VLLM_REF_TREE}"; \
    echo "vLLM pinned at ${VLLM_REF} / tree ${VLLM_REF_TREE}"

# ---- _C_stable_libtorch rebuilt for sm_121a (their build_stable_ext.sh) ----
# stable-only build: external_projects includes commented out, cutlass fetched
# at the pinned tag, Release build of the single C extension target.
RUN set -eux; \
    apt-get update -qq && apt-get install -y --no-install-recommends cmake ninja-build g++ >/dev/null && rm -rf /var/lib/apt/lists/*; \
    cd /src/vllm; \
    mkdir -p build/_deps/cutlass-src; \
    curl -sL -m 600 ${CUTLASS_URL} | tar xz -C build/_deps/cutlass-src --strip-components=1; \
    ls build/_deps/cutlass-src/include/cutlass/cutlass.h >/dev/null; \
    cp CMakeLists.txt CMakeLists.txt.orig; \
    sed -i -E "s|^(\s*)include\(cmake/external_projects/|\1# STABLE-ONLY: include(cmake/external_projects/|" CMakeLists.txt; \
    PYPATH=$(python3 -c "import sys;print(':'.join(p for p in sys.path if p))"); \
    TORCH_PREFIX=$(python3 -c "import torch;print(torch.utils.cmake_prefix_path)"); \
    NVRTC=$(ls /usr/local/cuda/lib64/libnvrtc.so /usr/local/cuda/lib64/libnvrtc.so.* /usr/local/lib/python3.12/dist-packages/nvidia/*/lib/libnvrtc.so* /usr/lib/aarch64-linux-gnu/libnvrtc.so* 2>/dev/null | head -1); \
    test -n "${NVRTC}"; \
    cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Release -DVLLM_TARGET_DEVICE=cuda \
        -DVLLM_PYTHON_EXECUTABLE=$(which python3) -DVLLM_PYTHON_PATH="$PYPATH" \
        -DFETCHCONTENT_BASE_DIR=build/_deps -DFETCHCONTENT_SOURCE_DIR_CUTLASS=build/_deps/cutlass-src \
        -DCMAKE_PREFIX_PATH="$TORCH_PREFIX" -DNVCC_THREADS=2 -DCUDA_nvrtc_LIBRARY="$NVRTC" -DTORCH_CUDA_ARCH_LIST=12.1a; \
    cmake --build build --target _C_stable_libtorch -j 20; \
    find build vllm -maxdepth 2 -name "_C_stable_libtorch*.so" | tee /tmp/stable_ext.so; \
    # place the built extension in the package dir (vLLM imports vllm._C_stable_libtorch)
    cp -v $(find build vllm -maxdepth 2 -name "_C_stable_libtorch*.so" | head -1) vllm/

# ---- merge the branch python tree (+ the sm121 .so) over the base install ----
RUN set -eux; \
    SITE=/usr/local/lib/python3.12/dist-packages/vllm; \
    cp -a /src/vllm/vllm/. ${SITE}/; \
    find ${SITE} -name "__pycache__" -type d -prune -exec rm -rf {} +; \
    python3 -c "import vllm; print('vllm', vllm.__version__)"; \
    # existence check only: dlopen of the CUDA extension needs libcuda.so.1
    # (GPU driver), which the build container does not have — the real import
    # happens at serve time on the GPU node
    ls ${SITE}/_C_stable_libtorch*.so

# ---- FlashInfer 0.7.0rc1 from pinned source (0.6.18 has no SM120 sparse-MLA) ----
RUN set -eux; \
    pip uninstall -y -q flashinfer-jit-cache flashinfer-cubin flashinfer-python || true; \
    rm -rf /root/.cache/flashinfer; \
    mkdir -p /opt/fi-src; \
    curl -sL -m 900 "https://codeload.github.com/flashinfer-ai/flashinfer/tar.gz/${FI_SHA}" | tar xz -C /opt/fi-src --strip-components=1; \
    mkdir -p /opt/fi-src/3rdparty/cutlass /opt/fi-src/3rdparty/cccl /opt/fi-src/3rdparty/spdlog; \
    curl -sL -m 900 "https://codeload.github.com/NVIDIA/cutlass/tar.gz/${FI_CUTLASS_SHA}" | tar xz -C /opt/fi-src/3rdparty/cutlass --strip-components=1; \
    curl -sL -m 900 "https://codeload.github.com/NVIDIA/cccl/tar.gz/${FI_CCCL_SHA}" | tar xz -C /opt/fi-src/3rdparty/cccl --strip-components=1; \
    curl -sL -m 900 "https://codeload.github.com/gabime/spdlog/tar.gz/${FI_SPDLOG_SHA}" | tar xz -C /opt/fi-src/3rdparty/spdlog --strip-components=1; \
    ls /opt/fi-src/3rdparty/cutlass/include/cutlass/cutlass.h /opt/fi-src/3rdparty/cccl/README.md /opt/fi-src/3rdparty/spdlog/include/spdlog/spdlog.h; \
    cd /opt/fi-src && BUILD_NVEP=0 FLASHINFER_BUILD_NO_PIP=1 pip install --no-deps --no-build-isolation -q .; \
    rm -rf /opt/fi-src/build; \
    python3 -c "import flashinfer; assert '0.7.0rc1' in flashinfer.__version__, flashinfer.__version__; print('flashinfer', flashinfer.__version__)"

ENV VLLM_HAS_FLASHINFER_CUBIN=1

# ---- prebuild the SM120 sparse-MLA + mxfp8 GEMM JIT kernels ----
# Exact runtime env (no verbose/debug nvcc flags — those change the cache key).
# The compiled modules persist in /root/.cache/flashinfer inside the image.
COPY patches/prewarm5.py /tmp/prewarm5.py
RUN set -eux; \
    FLASHINFER_CUDA_ARCH_LIST=12.1a TORCH_CUDA_ARCH_LIST=12.1a \
    FLASHINFER_DISABLE_VERSION_CHECK=1 VLLM_HAS_FLASHINFER_CUBIN=1 \
    FLASHINFER_NVCC_THREADS=1 MAX_JOBS=4 \
    python3 /tmp/prewarm5.py | tee /tmp/prewarm.log; \
    grep -q "SPARSE-BUILT+LOADED" /tmp/prewarm.log || { echo "sparse MLA prewarm FAILED"; exit 1; }

# ---- bake the 7 boot-10 patch files (md5-gated against their manifest) ----
COPY patches/ /tmp/patches/
RUN set -eux; \
    python3 - <<'PY'
import hashlib, os, shutil, sys
SITE = "/usr/local/lib/python3.12/dist-packages/vllm"
# md5 prefixes from tonyd2wild patch/README.md (boot-10 manifest)
MD5 = {
    "engram.py": "c0329107", "model_state.py": "0a14bee6",
    "weight_utils.py": "7e1027f1", "attention.py": "da9ef196",
    "flashinfer_sparse.py": "af0f8447", "sparse_swa.py": "cc419353",
    "sparse_attn_indexer.py": "a9b73756",
}
bad = []
for line in open("/tmp/patches/mounts.txt"):
    line = line.strip()
    if not line or line.startswith("#"):
        continue
    f, rel = line.split()
    src = f"/tmp/patches/{f}"
    m = hashlib.md5(open(src, "rb").read()).hexdigest()[:8]
    if m != MD5.get(f):
        bad.append(f"{f}: {m} != {MD5.get(f)}")
        continue
    dst = os.path.join(SITE, rel)
    os.makedirs(os.path.dirname(dst), exist_ok=True)
    shutil.copyfile(src, dst)
    print(f"baked {f} -> {rel} (md5 {m})")
sys.exit("\n".join(bad) if bad else None)
PY

# ---- final gates ----
RUN set -eux; \
    python3 -c "import vllm; print('vllm', vllm.__version__)"; \
    ls /usr/local/lib/python3.12/dist-packages/vllm/_C_stable_libtorch*.so; \
    python3 -c "import flashinfer; print('flashinfer', flashinfer.__version__)"; \
    grep -q "DSV41_ENGRAM_DISK" /usr/local/lib/python3.12/dist-packages/vllm/models/deepseek_v4_1/common/engram.py; \
    echo "=== image gates passed ==="
