From c4cf5897a94ca4ebe4b7a33caaf2463ff13055f1 Mon Sep 17 00:00:00 2001 From: Suhan Park Date: Thu, 13 Aug 2026 10:56:50 -0400 Subject: [PATCH 1/6] Publish test runtime image from firstclass --- .github/workflows/modal-runtime-image.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/modal-runtime-image.yml b/.github/workflows/modal-runtime-image.yml index c2c1950dc..ec3878d0b 100644 --- a/.github/workflows/modal-runtime-image.yml +++ b/.github/workflows/modal-runtime-image.yml @@ -3,7 +3,7 @@ name: Publish Modal runtime image on: workflow_dispatch: push: - branches: [master] + branches: [master, firstclass] tags: ["v*"] paths: - "docker/modal-runtime.Dockerfile" @@ -38,6 +38,7 @@ jobs: with: images: ghcr.io/borglab/gtsfm-modal-runtime tags: | + type=ref,event=branch type=ref,event=tag type=raw,value=latest,enable={{is_default_branch}} type=sha,prefix=sha- From 89e3395aad75d454870c9b66af84b4242919cb90 Mon Sep 17 00:00:00 2001 From: Suhan Park Date: Thu, 13 Aug 2026 10:59:32 -0400 Subject: [PATCH 2/6] Install runtime dependencies in image virtualenv --- docker/modal-runtime.Dockerfile | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/docker/modal-runtime.Dockerfile b/docker/modal-runtime.Dockerfile index 51b8b703e..6aab78327 100644 --- a/docker/modal-runtime.Dockerfile +++ b/docker/modal-runtime.Dockerfile @@ -33,8 +33,10 @@ RUN apt-get update \ RUN curl -LsSf https://astral.sh/uv/${UV_VERSION}/install.sh | sh \ && uv python install ${PYTHON_VERSION} \ - && ln -sf "$(uv python find ${PYTHON_VERSION})" /usr/local/bin/python \ - && ln -sf "$(uv python find ${PYTHON_VERSION})" /usr/local/bin/python3 + && uv venv --python ${PYTHON_VERSION} /opt/gtsfm-venv + +ENV VIRTUAL_ENV=/opt/gtsfm-venv \ + PATH=/opt/gtsfm-venv/bin:/root/.local/bin:${PATH} WORKDIR /opt/gtsfm-runtime COPY pyproject.toml uv.lock ./ @@ -46,7 +48,7 @@ RUN uv export \ --no-header \ --output-file /tmp/gtsfm-runtime-requirements.txt \ && uv pip install \ - --python /usr/local/bin/python \ + --python /opt/gtsfm-venv/bin/python \ --requirements /tmp/gtsfm-runtime-requirements.txt \ && rm /tmp/gtsfm-runtime-requirements.txt \ && python -c "import fastapi, gsplat, gtsam, spz, torch; print(torch.__version__)" From 3fa05587515bcbd70b50ac29b53bf5bc08f0e109 Mon Sep 17 00:00:00 2001 From: Suhan Park Date: Thu, 13 Aug 2026 11:02:19 -0400 Subject: [PATCH 3/6] Preserve CUDA package indexes in runtime image --- docker/modal-runtime.Dockerfile | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/docker/modal-runtime.Dockerfile b/docker/modal-runtime.Dockerfile index 6aab78327..fdbc14596 100644 --- a/docker/modal-runtime.Dockerfile +++ b/docker/modal-runtime.Dockerfile @@ -36,21 +36,16 @@ RUN curl -LsSf https://astral.sh/uv/${UV_VERSION}/install.sh | sh \ && uv venv --python ${PYTHON_VERSION} /opt/gtsfm-venv ENV VIRTUAL_ENV=/opt/gtsfm-venv \ + UV_PROJECT_ENVIRONMENT=/opt/gtsfm-venv \ PATH=/opt/gtsfm-venv/bin:/root/.local/bin:${PATH} WORKDIR /opt/gtsfm-runtime COPY pyproject.toml uv.lock ./ -RUN uv export \ +RUN uv sync \ --frozen \ --no-dev \ - --no-emit-project \ - --no-header \ - --output-file /tmp/gtsfm-runtime-requirements.txt \ - && uv pip install \ - --python /opt/gtsfm-venv/bin/python \ - --requirements /tmp/gtsfm-runtime-requirements.txt \ - && rm /tmp/gtsfm-runtime-requirements.txt \ + --no-install-project \ && python -c "import fastapi, gsplat, gtsam, spz, torch; print(torch.__version__)" ENV HF_HOME=/workspace/cache/huggingface \ From da197cacc6225efd490017c84a87e549382d95dd Mon Sep 17 00:00:00 2001 From: Suhan Park Date: Thu, 13 Aug 2026 11:20:42 -0400 Subject: [PATCH 4/6] Use system compression libraries for spz build --- docker/modal-runtime.Dockerfile | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docker/modal-runtime.Dockerfile b/docker/modal-runtime.Dockerfile index fdbc14596..04b1dcf2b 100644 --- a/docker/modal-runtime.Dockerfile +++ b/docker/modal-runtime.Dockerfile @@ -28,7 +28,9 @@ RUN apt-get update \ libglib2.0-0 \ libgomp1 \ libx11-6 \ + libzstd-dev \ ninja-build \ + zlib1g-dev \ && rm -rf /var/lib/apt/lists/* RUN curl -LsSf https://astral.sh/uv/${UV_VERSION}/install.sh | sh \ From ddce39cd342eece613329a1114943cc4708bf789 Mon Sep 17 00:00:00 2001 From: Suhan Park Date: Thu, 13 Aug 2026 11:40:18 -0400 Subject: [PATCH 5/6] Publish Modal runtime manually to Docker Hub --- .github/workflows/modal-runtime-image.yml | 55 ----------------------- README.md | 12 ++++- docker/publish-modal-runtime.sh | 21 +++++++++ visualization/modal_deployment.py | 2 +- 4 files changed, 32 insertions(+), 58 deletions(-) delete mode 100644 .github/workflows/modal-runtime-image.yml create mode 100755 docker/publish-modal-runtime.sh diff --git a/.github/workflows/modal-runtime-image.yml b/.github/workflows/modal-runtime-image.yml deleted file mode 100644 index ec3878d0b..000000000 --- a/.github/workflows/modal-runtime-image.yml +++ /dev/null @@ -1,55 +0,0 @@ -name: Publish Modal runtime image - -on: - workflow_dispatch: - push: - branches: [master, firstclass] - tags: ["v*"] - paths: - - "docker/modal-runtime.Dockerfile" - - "pyproject.toml" - - "uv.lock" - - ".github/workflows/modal-runtime-image.yml" - -permissions: - contents: read - packages: write - -concurrency: - group: modal-runtime-${{ github.ref }} - cancel-in-progress: true - -jobs: - publish: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - - uses: docker/setup-buildx-action@v3 - - - uses: docker/login-action@v3 - with: - registry: ghcr.io - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - - id: metadata - uses: docker/metadata-action@v5 - with: - images: ghcr.io/borglab/gtsfm-modal-runtime - tags: | - type=ref,event=branch - type=ref,event=tag - type=raw,value=latest,enable={{is_default_branch}} - type=sha,prefix=sha- - - - uses: docker/build-push-action@v6 - with: - context: . - file: docker/modal-runtime.Dockerfile - platforms: linux/amd64 - push: true - tags: ${{ steps.metadata.outputs.tags }} - labels: ${{ steps.metadata.outputs.labels }} - cache-from: type=gha - cache-to: type=gha,mode=max diff --git a/README.md b/README.md index feb7fb1a3..b9393eb7c 100644 --- a/README.md +++ b/README.md @@ -67,9 +67,17 @@ To submit work to a remote GPU machine, start its workspace with an API key: GTSFM_API_KEY=your-secret gtsfm run --host 0.0.0.0 --no-browser ``` -Choose **Remote VM** in the workspace, then select an API or SSH connection. Modal is the first hosted API option: enter its token ID and token secret separately, or paste the full `modal token set --token-id … --token-secret …` command into either field and the workspace will split it automatically. If the account does not already have a GTSFM deployment, click **Set up & deploy Modal workspace**. GTSFM first pulls the versioned `ghcr.io/borglab/gtsfm-modal-runtime` image, then deploys the protected `gtsfm-studio` web app and persistent `gtsfm-studio-data` volume, discovers its URL, and verifies the selected GPU. If the prebuilt image is unavailable, setup automatically falls back to building the dependency image in Modal. +Choose **Remote VM** in the workspace, then select an API or SSH connection. Modal is the first hosted API option: enter its token ID and token secret separately, or paste the full `modal token set --token-id … --token-secret …` command into either field and the workspace will split it automatically. If the account does not already have a GTSFM deployment, click **Set up & deploy Modal workspace**. GTSFM pulls the configured prebuilt Docker Hub runtime, then deploys the protected `gtsfm-studio` web app and persistent `gtsfm-studio-data` volume, discovers its URL, and verifies the selected GPU. If no prebuilt image is configured, setup builds the dependency image in Modal. -The prebuilt runtime contains the stable CUDA and Python dependency layers but no repository source or model weights. It is published by [the Modal runtime image workflow](.github/workflows/modal-runtime-image.yml) whenever runtime dependencies change on `master`, and for release tags. Normal GTSFM and UI changes are mounted after the image layer and therefore do not reinstall PyTorch, CUDA libraries, or `spz`. After its first publication, a Borglab package administrator must make `gtsfm-modal-runtime` public in GitHub Packages so Modal can pull it anonymously. To test a different runtime image, start the local workspace with `GTSFM_MODAL_RUNTIME_IMAGE=registry/image:tag gtsfm run`; set the variable to an empty string to force Modal's source-build fallback. +The prebuilt runtime contains the stable CUDA and Python dependency layers but no repository source or model weights. Normal GTSFM and UI changes are mounted after the image layer and therefore do not reinstall PyTorch, CUDA libraries, or `spz`. Publish it manually to a public Docker Hub repository using Modal's fast-pull eStargz format: + +```bash +docker login +./docker/publish-modal-runtime.sh su071301/gtsfm-modal-runtime firstclass +GTSFM_MODAL_RUNTIME_IMAGE=docker.io/su071301/gtsfm-modal-runtime:firstclass gtsfm run +``` + +The first command prompts for a Docker Hub username and access token. The publisher builds `linux/amd64`, pushes directly to Docker Hub, and does not require GitHub Actions. Set `GTSFM_MODAL_RUNTIME_IMAGE` to an empty string to force Modal's source-build fallback. GTSFM sample datasets are downloaded directly by the remote workspace. User-selected folders are archived and streamed to the protected workspace before the job starts. Live preview splats and the final `.ply` result are mirrored back to the local viewer. Modal account credentials and the derived workspace API key are kept in memory and are not included in public job state. Lambda Cloud, RunPod, Vast.ai, and AWS EC2 are listed as coming soon. diff --git a/docker/publish-modal-runtime.sh b/docker/publish-modal-runtime.sh new file mode 100755 index 000000000..fb5ceddc8 --- /dev/null +++ b/docker/publish-modal-runtime.sh @@ -0,0 +1,21 @@ +#!/usr/bin/env bash +set -euo pipefail + +if [[ $# -lt 1 || $# -gt 2 ]]; then + echo "Usage: $0 DOCKERHUB_NAMESPACE/gtsfm-modal-runtime [TAG]" >&2 + exit 2 +fi + +repository="${1#docker.io/}" +tag="${2:-firstclass}" +image="docker.io/${repository}:${tag}" + +docker info >/dev/null +docker buildx build \ + --platform linux/amd64 \ + --file docker/modal-runtime.Dockerfile \ + --tag "${image}" \ + --output type=registry,compression=estargz,force-compression=true,oci-mediatypes=true \ + . + +echo "Published ${image}" diff --git a/visualization/modal_deployment.py b/visualization/modal_deployment.py index 775eeb085..adde4decc 100644 --- a/visualization/modal_deployment.py +++ b/visualization/modal_deployment.py @@ -28,7 +28,7 @@ "H200", "B200", } -DEFAULT_MODAL_RUNTIME_IMAGE = "ghcr.io/borglab/gtsfm-modal-runtime:latest" +DEFAULT_MODAL_RUNTIME_IMAGE = "docker.io/su071301/gtsfm-modal-runtime:firstclass" def modal_remote_api_key(token_id: str, token_secret: str) -> str: From 2e549d3c3eaf422daa2b20dec33bc7e674148965 Mon Sep 17 00:00:00 2001 From: Suhan Park Date: Thu, 13 Aug 2026 19:00:58 -0400 Subject: [PATCH 6/6] Keep Modal volume mount paths empty --- docker/modal-runtime.Dockerfile | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/docker/modal-runtime.Dockerfile b/docker/modal-runtime.Dockerfile index 04b1dcf2b..daec1f79d 100644 --- a/docker/modal-runtime.Dockerfile +++ b/docker/modal-runtime.Dockerfile @@ -50,10 +50,9 @@ RUN uv sync \ --no-install-project \ && python -c "import fastapi, gsplat, gtsam, spz, torch; print(torch.__version__)" -ENV HF_HOME=/workspace/cache/huggingface \ - TORCH_HOME=/workspace/cache/torch \ - TORCH_EXTENSIONS_DIR=/workspace/cache/torch-extensions \ - XDG_CACHE_HOME=/workspace/cache \ - PYTHONPATH=/root +# Volume-backed cache locations are assigned by visualization/modal_app.py at +# container startup. They must not be present while Modal extends this image, +# because build tools can otherwise populate the future Volume mount target. +ENV PYTHONPATH=/root WORKDIR /root