transformer / deploy_to_hf.py
Mohith202's picture
Sync project + blog.md to Space
75f048e verified
Raw
History Blame Contribute Delete
5.96 kB
#!/usr/bin/env python
"""Deploy the BrainRL OpenEnv server to a Hugging Face Space.
Pushes the project as a Docker-SDK Space (the README.md frontmatter already
declares ``sdk: docker`` and ``app_port: 8000``). The local trainer/client can
then point at the Space URL to interact with the deployed environment.
Examples
--------
huggingface-cli login # or set HF_TOKEN
python deploy_to_hf.py \\
--repo-id mohith202/transformer \\
--include-manifest
# Public Space:
python deploy_to_hf.py --repo-id you/brainrl-region-selection --public
After deploy, the Space URL is printed. Wire your client to it via:
import os
os.environ["BRAINRL_API_URL"] = "https://<user>-<space-name>.hf.space"
"""
from __future__ import annotations
import argparse
import os
import sys
from pathlib import Path
PROJECT_ROOT = Path(__file__).resolve().parent
DEFAULT_INCLUDE = [
"README.md",
"blog.md",
"Dockerfile",
"openenv.yaml",
"pyproject.toml",
"requirements.txt",
"Makefile",
"__init__.py",
"client.py",
"models.py",
"data_loader.py",
"data_split.py",
"stimulus_loader.py",
"prompts.py",
"rewards.py",
"metrics.py",
"baselines.py",
"evaluate.py",
"inference.py",
"train_grpo.py",
"prepare_parcels.py",
"plotting.py",
"hf_data.py",
"hf_jobs.py",
"deploy_to_hf.py",
"configs/**",
"notebooks/**",
"server/**",
# server/** already covers prediction_ui.py, but list it explicitly so a
# selective deploy that overrides DEFAULT_INCLUDE still picks it up.
"server/prediction_ui.py",
]
DEFAULT_IGNORE = [
"**/__pycache__/**",
"**/*.pyc",
"*.egg-info",
"*.egg-info/**",
"outputs/**",
"build/**",
"dist/**",
".git/**",
".venv/**",
"venv/**",
"*.nii",
"*.nii.gz",
"*.npy",
"*.npz",
]
def build_parser() -> argparse.ArgumentParser:
parser = argparse.ArgumentParser(description="Deploy BrainRL to a HF Space")
parser.add_argument(
"--repo-id",
type=str,
required=True,
help="<user_or_org>/<space_name> (e.g. mohith/brainrl-region-selection)",
)
parser.add_argument(
"--token",
type=str,
default=None,
help="HF token (defaults to $HF_TOKEN / huggingface-cli login).",
)
parser.add_argument(
"--public",
action="store_true",
help="Create the Space as public (default: private).",
)
parser.add_argument(
"--commit-message",
type=str,
default="Deploy BrainRL OpenEnv server",
)
parser.add_argument(
"--include-manifest",
action="store_true",
help="Include configs/parcel_candidates.json (run prepare_parcels.py first).",
)
parser.add_argument(
"--dry-run",
action="store_true",
help="Print what would be uploaded without actually pushing.",
)
return parser
def _resolve_token(arg_token: str | None) -> str | None:
return arg_token or os.environ.get("HF_TOKEN") or os.environ.get("HUGGING_FACE_HUB_TOKEN")
def _check_manifest(include_manifest: bool) -> None:
manifest = PROJECT_ROOT / "configs" / "parcel_candidates.json"
if include_manifest and not manifest.exists():
raise SystemExit(
"configs/parcel_candidates.json is missing. Run `make prepare` first or "
"drop --include-manifest."
)
if not include_manifest and manifest.exists():
print(
f"[note] {manifest} exists locally but will NOT be uploaded "
"(pass --include-manifest to ship it)."
)
def _build_ignore(include_manifest: bool) -> list[str]:
ignore = list(DEFAULT_IGNORE)
if not include_manifest:
ignore.append("configs/parcel_candidates.json")
return ignore
def _build_allow(include_manifest: bool) -> list[str]:
allow = list(DEFAULT_INCLUDE)
if include_manifest:
allow.append("configs/parcel_candidates.json")
return allow
def main() -> None:
args = build_parser().parse_args()
try:
from huggingface_hub import HfApi, create_repo, upload_folder
except ImportError as exc: # pragma: no cover
raise SystemExit(
"huggingface_hub is required. Install with `pip install -e .[deploy]` "
"or `pip install huggingface_hub`."
) from exc
token = _resolve_token(args.token)
_check_manifest(args.include_manifest)
allow = _build_allow(args.include_manifest)
ignore = _build_ignore(args.include_manifest)
if args.dry_run:
print(f"[dry-run] repo_id={args.repo_id} private={not args.public}")
print("[dry-run] would upload (allow patterns):")
for entry in allow:
print(f" + {entry}")
print("[dry-run] would ignore patterns:")
for entry in ignore:
print(f" - {entry}")
return
print(f"[1/3] Ensuring HF Space {args.repo_id} exists (sdk=docker)...")
create_repo(
repo_id=args.repo_id,
repo_type="space",
space_sdk="docker",
private=not args.public,
exist_ok=True,
token=token,
)
print(f"[2/3] Uploading project from {PROJECT_ROOT}...")
upload_folder(
folder_path=str(PROJECT_ROOT),
repo_id=args.repo_id,
repo_type="space",
token=token,
commit_message=args.commit_message,
allow_patterns=allow,
ignore_patterns=ignore,
)
space_url = f"https://huggingface.co/spaces/{args.repo_id}"
api_url = f"https://{args.repo_id.replace('/', '-')}.hf.space"
print(f"[3/3] Done. Space: {space_url}")
print(f" Live API URL: {api_url}")
print(f" Set BRAINRL_API_URL={api_url} for clients/inference.")
print(" First boot may take a few minutes while HF builds the Docker image.")
if __name__ == "__main__":
sys.exit(main())