Spaces:
Running on Zero
Running on Zero
| # syntax=docker/dockerfile:1.6 | |
| # | |
| # animoflow-app β HF Space + OSS local Docker image. | |
| # | |
| # Single-venv orchestrator with warm MDM-family models loaded at module level. | |
| # Per-(comfy+model) escape-hatch venvs isolate outlier models | |
| # (Kimodo NVIDIA stack, future additions). | |
| # | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # AUTH: this Dockerfile uses two BuildKit secrets, both never landing | |
| # in any image layer: | |
| # | |
| # gh_token β read access to AnimoFlow/{comfyui-animoflow,animoflow-api} | |
| # (currently private; the secret-mount keeps working | |
| # after either repo flips public). | |
| # hf_token β read access to AnimoFlow/animoflow-checkpoints (the | |
| # private HF Hub model repo where MDM weights live; baked | |
| # into the image at build time). | |
| # | |
| # HF Spaces autopopulate Space-level secrets with these names. For local | |
| # builds you pass them via `--secret id=<name>,src=<path-to-token-file>`. | |
| # | |
| # Mac local build: | |
| # gh auth token > /tmp/gh_token | |
| # DOCKER_BUILDKIT=1 docker build \ | |
| # --secret id=gh_token,src=/tmp/gh_token \ | |
| # -t animoflow/app . | |
| # rm /tmp/gh_token | |
| # | |
| # HF Space build: configure a Space secret named `gh_token` in | |
| # Settings β Variables and secrets. HF passes it to the build automatically. | |
| # | |
| # Run (OSS local, after build): | |
| # docker run --rm -p 7860:7860 animoflow/app | |
| # | |
| # Build args let you pin upstream SHAs reproducibly. CI bumps these | |
| # deliberately, never via `git pull` behind our back (wrap upstream | |
| # repos, don't fork them). | |
| ARG PYTHON_VERSION=3.11 | |
| ARG BLENDER_VERSION=4.2.5 | |
| ARG COMFYUI_ANIMOFLOW_REPO=https://github.com/AnimoFlow/comfyui-animoflow.git | |
| ARG ANIMOFLOW_API_REPO=https://github.com/AnimoFlow/animoflow-api.git | |
| ARG MDM_REPO=https://github.com/GuyTevet/motion-diffusion-model.git | |
| ARG MOMASK_REPO=https://github.com/EricGuo5513/momask-codes.git | |
| # SHA pins β update deliberately, not via `git pull` | |
| ARG COMFYUI_ANIMOFLOW_SHA=main | |
| ARG ANIMOFLOW_API_SHA=main | |
| ARG MDM_SHA=main | |
| ARG MOMASK_SHA=main | |
| # Baked checkpoints β pulled from a private HF Hub model repo at build | |
| # time using the `hf_token` BuildKit secret. SHA-pinned for reproducibility; | |
| # bump deliberately. The repo holds full training artifacts (final + earlier | |
| # checkpoints + optimizer state + eval logs) but the Dockerfile filters via | |
| # `allow_patterns` to bake only the inference-essential files. | |
| ARG ANIMOFLOW_CHECKPOINTS_REPO=AnimoFlow/animoflow-checkpoints | |
| ARG ANIMOFLOW_CHECKPOINTS_REVISION=9c8b352f849c5e81869a50f71667d43c064571eb | |
| FROM python:${PYTHON_VERSION}-slim AS base | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # System deps | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| ENV DEBIAN_FRONTEND=noninteractive | |
| RUN apt-get update && apt-get install -y --no-install-recommends \ | |
| git curl wget ca-certificates xz-utils \ | |
| xvfb xauth \ | |
| libxi6 libxrender1 libxxf86vm1 libxfixes3 libxkbcommon0 \ | |
| libsm6 libxext6 libgl1 libglu1-mesa libegl1 \ | |
| && rm -rf /var/lib/apt/lists/* | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # Blender 4.2 LTS (pinned tarball β apt's blender lags 3.x). | |
| # Arch-aware: Docker BuildKit auto-populates TARGETARCH (amd64 / arm64). | |
| # Default `docker build` (no buildx) leaves it empty and falls through to | |
| # amd64, matching HF Spaces (x86_64 H200 hosts). | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| ARG BLENDER_VERSION | |
| ARG TARGETARCH | |
| RUN BLENDER_MAJ=$(echo "${BLENDER_VERSION}" | cut -d. -f1,2) \ | |
| && case "${TARGETARCH:-amd64}" in \ | |
| amd64|x86_64|"") BLENDER_ARCH=linux-x64 ;; \ | |
| arm64|aarch64) BLENDER_ARCH=linux-arm64 ;; \ | |
| *) echo "Unsupported TARGETARCH=${TARGETARCH}" >&2 ; exit 1 ;; \ | |
| esac \ | |
| && echo "Building for ${TARGETARCH:-amd64} β blender-${BLENDER_VERSION}-${BLENDER_ARCH}.tar.xz" \ | |
| && curl -fsSL "https://download.blender.org/release/Blender${BLENDER_MAJ}/blender-${BLENDER_VERSION}-${BLENDER_ARCH}.tar.xz" -o /tmp/blender.tar.xz \ | |
| && mkdir -p /opt/blender \ | |
| && tar -xf /tmp/blender.tar.xz -C /opt/blender --strip-components=1 \ | |
| && rm /tmp/blender.tar.xz \ | |
| && /opt/blender/blender --version | |
| ENV BLENDER_BIN=/opt/blender/blender | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # Clone wrapper repos β wrap-don't-modify, pinned by SHA build args. | |
| # | |
| # Both `comfyui-animoflow` and `animoflow-api` are currently private. We | |
| # clone both via a BuildKit secret named `gh_token` (the token is | |
| # interpolated into the URL temporarily and never lands in any image | |
| # layer β we strip the remote URL after the clone). The same pattern | |
| # works once either repo is flipped to public, so this keeps working | |
| # across the public flip. | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| ARG COMFYUI_ANIMOFLOW_REPO | |
| ARG COMFYUI_ANIMOFLOW_SHA | |
| ARG ANIMOFLOW_API_REPO | |
| ARG ANIMOFLOW_API_SHA | |
| RUN --mount=type=secret,id=gh_token,required=true \ | |
| GH_TOKEN="$(cat /run/secrets/gh_token)" \ | |
| && _AUTH_CAF=$(echo "${COMFYUI_ANIMOFLOW_REPO}" | sed -e "s|https://|https://x-access-token:${GH_TOKEN}@|") \ | |
| && _AUTH_AFW=$(echo "${ANIMOFLOW_API_REPO}" | sed -e "s|https://|https://x-access-token:${GH_TOKEN}@|") \ | |
| && git clone "${_AUTH_CAF}" /opt/comfyui-animoflow \ | |
| && git -C /opt/comfyui-animoflow checkout "${COMFYUI_ANIMOFLOW_SHA}" \ | |
| && git -C /opt/comfyui-animoflow remote set-url origin "${COMFYUI_ANIMOFLOW_REPO}" \ | |
| && git clone "${_AUTH_AFW}" /opt/animoflow-api \ | |
| && git -C /opt/animoflow-api checkout "${ANIMOFLOW_API_SHA}" \ | |
| && git -C /opt/animoflow-api remote set-url origin "${ANIMOFLOW_API_REPO}" \ | |
| && unset GH_TOKEN _AUTH_CAF _AUTH_AFW | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # Clone upstream models. | |
| # priorMDM and Kimodo come in later iterations. | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| ARG MDM_REPO | |
| ARG MDM_SHA | |
| ARG MOMASK_REPO | |
| ARG MOMASK_SHA | |
| RUN git clone "${MDM_REPO}" /opt/mdm-codes \ | |
| && git -C /opt/mdm-codes checkout "${MDM_SHA}" | |
| RUN git clone "${MOMASK_REPO}" /opt/momask-codes \ | |
| && git -C /opt/momask-codes checkout "${MOMASK_SHA}" | |
| ENV MDM_PATH=/opt/mdm-codes | |
| ENV MOMASK_PATH=/opt/momask-codes | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # Single Python venv (orchestrator + MDM family). | |
| # `uv venv` creates a bare virtualenv (no pip bundled by default β uv | |
| # expects `uv pip install --python <venv>` instead of bare pip). Use the | |
| # `uv pip` form throughout. | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| RUN pip install --no-cache-dir uv==0.5.11 | |
| # Create venv + install orchestrator/MDM-family deps. | |
| # | |
| # `--seed` populates the venv with pip + setuptools + wheel, and | |
| # `--no-build-isolation` makes uv build sdists against the target venv | |
| # (which has those tools) instead of uv's pip-less isolated build env. | |
| # That combo handles legacy packages whose setup.py imports pip internals, | |
| # without disabling build isolation everywhere. | |
| COPY requirements.txt /opt/animoflow-app/requirements.txt | |
| RUN uv venv /opt/venvs/api --python ${PYTHON_VERSION} --seed \ | |
| && uv pip install --python /opt/venvs/api --no-cache --no-build-isolation \ | |
| -r /opt/animoflow-app/requirements.txt | |
| # NOTE: we deliberately do NOT install | |
| # `comfyui-animoflow/containers/mdm/requirements.txt` here. That file pins | |
| # torch==2.3.0 which downgrades our 2.4.1 pin and removes | |
| # `torch.library.register_fake` β an API the upstream MDM source uses, so | |
| # the wrapper falls back to placeholder mode. | |
| # | |
| # Instead we lift the MDM-container deps that aren't already in our pin | |
| # (einops) directly into requirements.txt above. The local Docker | |
| # stack uses a different topology (each model in its own container with | |
| # its own venv) so the version skew there doesn't bite. | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # Bake model checkpoints from the AnimoFlow/animoflow-checkpoints HF Hub | |
| # model repo. Token only mounted for the duration of this RUN β never | |
| # lands in any image layer. Failure here aborts the build (we don't want | |
| # to ship an HF Space without weights). | |
| # | |
| # To override the source repo or pin a specific revision, pass build args: | |
| # --build-arg ANIMOFLOW_CHECKPOINTS_REPO=AnimoFlow/different-repo | |
| # --build-arg ANIMOFLOW_CHECKPOINTS_REVISION=<commit-sha> | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| ARG ANIMOFLOW_CHECKPOINTS_REPO | |
| ARG ANIMOFLOW_CHECKPOINTS_REVISION | |
| RUN --mount=type=secret,id=hf_token,required=true \ | |
| export HF_TOKEN="$(cat /run/secrets/hf_token)" \ | |
| && echo "Downloading checkpoints from ${ANIMOFLOW_CHECKPOINTS_REPO}@${ANIMOFLOW_CHECKPOINTS_REVISION}β¦" \ | |
| && /opt/venvs/api/bin/python -c "\ | |
| import os; \ | |
| from huggingface_hub import snapshot_download; \ | |
| path = snapshot_download( \ | |
| repo_id='${ANIMOFLOW_CHECKPOINTS_REPO}', \ | |
| revision='${ANIMOFLOW_CHECKPOINTS_REVISION}', \ | |
| repo_type='model', \ | |
| local_dir='/opt/checkpoints', \ | |
| allow_patterns=[ \ | |
| 'humanml_enc_512_50steps/model000750000.pt', \ | |
| 'humanml_enc_512_50steps/args.json', \ | |
| ], \ | |
| token=os.environ['HF_TOKEN']); \ | |
| print('checkpoints baked at', path)" \ | |
| && unset HF_TOKEN \ | |
| && cp /opt/comfyui-animoflow/containers/mdm/t2m_mean.npy /opt/checkpoints/t2m_mean.npy \ | |
| && cp /opt/comfyui-animoflow/containers/mdm/t2m_std.npy /opt/checkpoints/t2m_std.npy \ | |
| && ls -laR /opt/checkpoints/ \ | |
| && du -sh /opt/checkpoints/ | |
| # WEIGHTS_DIR is what comfyui-animoflow/containers/mdm/inference.py reads for | |
| # both the model checkpoint AND the t2m_mean/std normalization arrays. | |
| # Setting it as ENV makes the MDM wrapper find them without per-run config. | |
| ENV WEIGHTS_DIR=/opt/checkpoints | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # Multilingual prompt rewriter β Qwen2.5-1.5B-Instruct + RAFSL few-shot | |
| # from the local HumanML3D caption corpus. Phase-0 verified at 90% pass | |
| # rate / 0.32s server-only on ZeroGPU. See: | |
| # - animoflow-api/api/rewriter.py (the consumer) | |
| # | |
| # All three assets SHA-pinned. Bump deliberately, not via "latest". | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| ARG REWRITER_MODEL_REPO=Qwen/Qwen2.5-1.5B-Instruct | |
| ARG REWRITER_MODEL_REVISION=989aa7980e4c | |
| ARG REWRITER_RETRIEVER_REPO=sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2 | |
| ARG REWRITER_RETRIEVER_REVISION=e8f8c211226b | |
| ARG REWRITER_CORPUS_REPO=AnimoFlow/rewriter-corpus | |
| ARG REWRITER_CORPUS_REVISION=5b98da4d2db075d40afd59badf51f2eefe1db5e2 | |
| RUN /opt/venvs/api/bin/python -c "\ | |
| from huggingface_hub import snapshot_download; \ | |
| snapshot_download( \ | |
| repo_id='${REWRITER_MODEL_REPO}', \ | |
| revision='${REWRITER_MODEL_REVISION}', \ | |
| local_dir='/opt/rewriter/qwen', \ | |
| allow_patterns=['*.safetensors','*.json','tokenizer*','*.model','*.txt','vocab*','merges*'], \ | |
| ); \ | |
| snapshot_download( \ | |
| repo_id='${REWRITER_RETRIEVER_REPO}', \ | |
| revision='${REWRITER_RETRIEVER_REVISION}', \ | |
| local_dir='/opt/rewriter/minilm', \ | |
| allow_patterns=['*.safetensors','*.json','tokenizer*','*.txt','vocab*','*.bin'], \ | |
| ); \ | |
| snapshot_download( \ | |
| repo_id='${REWRITER_CORPUS_REPO}', \ | |
| revision='${REWRITER_CORPUS_REVISION}', \ | |
| repo_type='dataset', \ | |
| local_dir='/opt/rewriter/corpus', \ | |
| allow_patterns=['captions.json','embeddings.npy'], \ | |
| ); \ | |
| print('rewriter baked at /opt/rewriter')" \ | |
| && ls -laR /opt/rewriter/ \ | |
| && du -sh /opt/rewriter/* | |
| ENV REWRITER_DATA_DIR=/opt/rewriter | |
| ENV PATH=/opt/venvs/api/bin:${PATH} | |
| ENV PYTHONUNBUFFERED=1 | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # Copy the orchestrator code last (cache-friendly) | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| COPY . /opt/animoflow-app | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # Runtime config | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| ENV OUTPUT_DIR=/tmp/animoflow-output | |
| ENV CHECKPOINTS_DIR=/opt/checkpoints | |
| ENV CHARACTERS_DIR=/opt/comfyui-animoflow/characters | |
| ENV WEB_DIR=/nonexistent | |
| # Bogus model-server URLs β animoflow-api's health poller will log "unreachable" | |
| # but no separate model-server processes run on HF. Harmless. | |
| ENV MDM_ENDPOINT=http://127.0.0.1:65500 | |
| ENV PRIORMDM_ENDPOINT=http://127.0.0.1:65501 | |
| ENV MOMASK_ENDPOINT=http://127.0.0.1:65502 | |
| ENV KIMODO_ENDPOINT=http://127.0.0.1:65504 | |
| ENV COMFYUI_URL=http://127.0.0.1:65505 | |
| ENV HEALTH_POLL_INTERVAL=600 | |
| ENV PORT=7860 | |
| RUN mkdir -p ${OUTPUT_DIR} ${CHECKPOINTS_DIR} \ | |
| && chmod +x /opt/animoflow-app/entrypoint.sh /opt/animoflow-app/scripts/*.sh | |
| EXPOSE 7860 | |
| WORKDIR /opt/animoflow-app | |
| # Healthcheck via the FastAPI health endpoint (exposed even before Gradio mounts) | |
| HEALTHCHECK --interval=30s --timeout=5s --start-period=120s --retries=3 \ | |
| CMD curl -fsS http://localhost:7860/v1/health || exit 1 | |
| CMD ["/opt/animoflow-app/entrypoint.sh"] | |