diff --git a/pyproject.toml b/pyproject.toml index e8e3e2de..c9ee498f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -48,6 +48,7 @@ docs = [ "pillow>=10.0.0", "cairosvg>=2.7.1" ] +hf = ["huggingface-hub>=0.20.0"] zarr_conversion = [ "fire>=0.5.0", "numcodecs>=0.16.3", diff --git a/src/electrai/callbacks/__init__.py b/src/electrai/callbacks/__init__.py new file mode 100644 index 00000000..9d84fafb --- /dev/null +++ b/src/electrai/callbacks/__init__.py @@ -0,0 +1,5 @@ +from __future__ import annotations + +from electrai.callbacks.hf_upload import HuggingFaceCallback + +__all__ = ["HuggingFaceCallback"] diff --git a/src/electrai/callbacks/hf_upload.py b/src/electrai/callbacks/hf_upload.py new file mode 100644 index 00000000..b8ec2e20 --- /dev/null +++ b/src/electrai/callbacks/hf_upload.py @@ -0,0 +1,182 @@ +from __future__ import annotations + +import json +import logging +from pathlib import Path +from typing import TYPE_CHECKING + +from lightning.pytorch.callbacks import Callback + +if TYPE_CHECKING: + from types import SimpleNamespace + +logger = logging.getLogger(__name__) + +MANIFEST_FILENAME = "hf_upload_manifest.json" + + +class HuggingFaceCallback(Callback): + """Tracks saved checkpoints for deferred upload to HuggingFace Hub. + + On clusters without internet (e.g. Princeton Della), checkpoints are + queued in a JSON manifest and uploaded later via ``electrai hf-push``. + When ``hf.upload_immediate`` is True, uploads are attempted inline + (failures are logged but never crash training). + """ + + def __init__(self, cfg: SimpleNamespace) -> None: + super().__init__() + hf = cfg.hf + self.repo_id: str = hf["repo_id"] + self.every_n_epochs: int = hf.get("upload_every_n_epochs", 5) + self.upload_immediate: bool = hf.get("upload_immediate", False) + self.ckpt_path = Path(getattr(cfg, "ckpt_path", "./checkpoints")) + self.manifest_path = self.ckpt_path / MANIFEST_FILENAME + self._manifest: list[dict] = [] + self._load_existing_manifest() + + def _load_existing_manifest(self) -> None: + if self.manifest_path.exists(): + with self.manifest_path.open(encoding="utf-8") as f: + self._manifest = json.load(f) + + def _save_manifest(self) -> None: + self.ckpt_path.mkdir(parents=True, exist_ok=True) + with self.manifest_path.open("w", encoding="utf-8") as f: + json.dump(self._manifest, f, indent=2) + + def _queue_checkpoint( + self, ckpt_file: Path, epoch: int | None, *, path_in_repo: str | None = None + ) -> None: + entry = { + "path": str(ckpt_file), + "path_in_repo": path_in_repo or ckpt_file.name, + "epoch": epoch, + "repo_id": self.repo_id, + "uploaded": False, + } + self._manifest.append(entry) + self._save_manifest() + logger.info("Queued checkpoint for HF upload: %s", ckpt_file.name) + + def on_validation_end(self, trainer, pl_module) -> None: # noqa: ARG002 + if trainer.sanity_checking: + return + epoch = trainer.current_epoch + if (epoch + 1) % self.every_n_epochs != 0: + return + if trainer.global_rank != 0: + return + + # Save the current state to a stable epoch-specific file. This is + # independent of ModelCheckpoint's last.ckpt (which Lightning reorders + # to run after us in this hook, so last.ckpt would still be stale). + stable_name = f"last_epoch{epoch + 1:03d}.ckpt" + stable_path = self.ckpt_path / stable_name + trainer.save_checkpoint(stable_path) + + self._queue_checkpoint(stable_path, epoch, path_in_repo=stable_name) + + if self.upload_immediate: + _upload_single(self._manifest[-1]) + if self._manifest[-1]["uploaded"]: + stable_path.unlink(missing_ok=True) + self._save_manifest() + + def on_train_end(self, trainer, pl_module) -> None: # noqa: ARG002 + if trainer.global_rank != 0: + return + # Queue best checkpoints that haven't been queued yet + queued_paths = {e["path"] for e in self._manifest} + had_immediate = False + for ckpt_file in self.ckpt_path.glob("ckpt_*.ckpt"): + if str(ckpt_file) not in queued_paths: + self._queue_checkpoint(ckpt_file, epoch=None) + if self.upload_immediate: + _upload_single(self._manifest[-1]) + had_immediate = True + if had_immediate: + self._save_manifest() + + pending = sum(1 for e in self._manifest if not e["uploaded"]) + if pending: + logger.info( + "%d checkpoint(s) pending upload. " + "Run 'electrai hf-push --ckpt-path %s' from a node with " + "internet access.", + pending, + self.ckpt_path, + ) + + +def _upload_single(entry: dict) -> None: + """Attempt to upload a single checkpoint. Logs errors, never raises.""" + path = Path(entry["path"]) + try: + from huggingface_hub import upload_file + except ImportError: + logger.warning( + "huggingface-hub is not installed. " + "Run 'pip install huggingface-hub' to enable uploads." + ) + return + try: + if not path.exists(): + logger.warning("Checkpoint file not found, skipping: %s", path) + return + path_in_repo = entry.get("path_in_repo", path.name) + upload_file( + path_or_fileobj=str(path), + path_in_repo=path_in_repo, + repo_id=entry["repo_id"], + ) + entry["uploaded"] = True + logger.info("Uploaded %s to %s", path.name, entry["repo_id"]) + except Exception: + logger.warning( + "HF upload failed for %s (will retry with hf-push)", + path.name, + exc_info=True, + ) + + +def hf_push(ckpt_path: str, *, clean: bool = False) -> None: + """Upload pending checkpoints from a manifest file. + + Run this from a login node or machine with internet access. + """ + try: + import huggingface_hub # noqa: F401 + except ImportError as e: + raise SystemExit( + "huggingface-hub is not installed. " + "Run 'uv sync --extra hf' to enable uploads." + ) from e + + ckpt_dir = Path(ckpt_path) + manifest_path = ckpt_dir / MANIFEST_FILENAME + if not manifest_path.exists(): + raise SystemExit(f"No manifest found at {manifest_path}") + + with manifest_path.open(encoding="utf-8") as f: + manifest = json.load(f) + + pending = [e for e in manifest if not e["uploaded"]] + if not pending: + logger.info("All checkpoints already uploaded.") + return + + logger.info("Uploading %d pending checkpoint(s)...", len(pending)) + for entry in pending: + _upload_single(entry) + if clean and entry["uploaded"]: + Path(entry["path"]).unlink(missing_ok=True) + + with manifest_path.open("w", encoding="utf-8") as f: + json.dump(manifest, f, indent=2) + + still_pending = sum(1 for e in manifest if not e["uploaded"]) + if still_pending: + logger.warning("%d checkpoint(s) still failed to upload.", still_pending) + else: + logger.info("All checkpoints uploaded successfully.") diff --git a/src/electrai/configs/MP/config_resnet.yaml b/src/electrai/configs/MP/config_resnet.yaml index 58938fa3..834ba84d 100644 --- a/src/electrai/configs/MP/config_resnet.yaml +++ b/src/electrai/configs/MP/config_resnet.yaml @@ -43,6 +43,12 @@ wb_pname: mp-experiment # checkpoints ckpt_path: ./checkpoints +# HuggingFace Hub (optional — install with `uv sync --extra hf`) +# hf: +# repo_id: your-username/your-repo # must already exist on HF +# upload_every_n_epochs: 5 +# upload_immediate: false # set true on nodes with internet access + # test the model # save_pred: true # log_dir: ./logs diff --git a/src/electrai/configs/MP/config_resunet.yaml b/src/electrai/configs/MP/config_resunet.yaml index a16049cc..7fef6ff4 100644 --- a/src/electrai/configs/MP/config_resunet.yaml +++ b/src/electrai/configs/MP/config_resunet.yaml @@ -43,6 +43,12 @@ wb_pname: mp-experiment # checkpoints ckpt_path: ./checkpoints +# HuggingFace Hub (optional — install with `uv sync --extra hf`) +# hf: +# repo_id: your-username/your-repo # must already exist on HF +# upload_every_n_epochs: 5 +# upload_immediate: false # set true on nodes with internet access + # test the model # save_pred: true # log_dir: ./logs diff --git a/src/electrai/entrypoints/main.py b/src/electrai/entrypoints/main.py index 6ccc4725..f8f9e791 100644 --- a/src/electrai/entrypoints/main.py +++ b/src/electrai/entrypoints/main.py @@ -1,6 +1,7 @@ from __future__ import annotations import argparse +import logging import torch @@ -23,6 +24,7 @@ def main() -> None: RuntimeError if no command was input """ + logging.basicConfig(level=logging.INFO) parser = argparse.ArgumentParser(description="Electrai Entry Point") subparsers = parser.add_subparsers(dest="command", required=True) @@ -32,14 +34,28 @@ def main() -> None: test_parser = subparsers.add_parser("test", help="Evaluate the model") test_parser.add_argument("--config", type=str, required=True) + hf_push_parser = subparsers.add_parser( + "hf-push", help="Upload pending checkpoints to HuggingFace Hub" + ) + hf_push_parser.add_argument( + "--ckpt-path", type=str, required=True, help="Path to checkpoint directory" + ) + hf_push_parser.add_argument( + "--clean", + action="store_true", + help="Delete local checkpoint files after successful upload (includes best-model checkpoints)", + ) + args = parser.parse_args() if args.command == "train": train(args) elif args.command == "test": test(args) - else: - raise ValueError(f"Unknown command: {args.command}") + elif args.command == "hf-push": + from electrai.callbacks.hf_upload import hf_push + + hf_push(args.ckpt_path, clean=args.clean) if __name__ == "__main__": diff --git a/src/electrai/entrypoints/train.py b/src/electrai/entrypoints/train.py index d51d6777..d841519e 100644 --- a/src/electrai/entrypoints/train.py +++ b/src/electrai/entrypoints/train.py @@ -58,6 +58,14 @@ def train(args): lr_monitor = LearningRateMonitor(logging_interval="epoch") + callbacks = [checkpoint_cb, lr_monitor] + + hf_cfg = getattr(cfg, "hf", None) + if hf_cfg and hf_cfg.get("repo_id"): + from electrai.callbacks.hf_upload import HuggingFaceCallback + + callbacks.append(HuggingFaceCallback(cfg)) + # ----------------------------- # Trainer # ----------------------------- @@ -69,7 +77,7 @@ def train(args): trainer = Trainer( max_epochs=int(cfg.epochs), logger=wandb_logger, - callbacks=[checkpoint_cb, lr_monitor], + callbacks=callbacks, accelerator="gpu" if torch.cuda.is_available() else "cpu", precision=cfg.precision, devices="auto", diff --git a/uv.lock b/uv.lock index f7d606fa..624e21dc 100644 --- a/uv.lock +++ b/uv.lock @@ -161,6 +161,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl", hash = "sha256:053243f8b92b990551949e63930a839ff0cf0b0ebbe0597b0f3fb19e1a0fe82e", size = 7490, upload-time = "2025-07-03T22:54:42.156Z" }, ] +[[package]] +name = "annotated-doc" +version = "0.0.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/57/ba/046ceea27344560984e26a590f90bc7f4a75b06701f653222458922b558c/annotated_doc-0.0.4.tar.gz", hash = "sha256:fbcda96e87e9c92ad167c2e53839e57503ecfda18804ea28102353485033faa4", size = 7288, upload-time = "2025-11-10T22:07:42.062Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/d3/26bf1008eb3d2daa8ef4cacc7f3bfdc11818d111f7e2d0201bc6e3b49d45/annotated_doc-0.0.4-py3-none-any.whl", hash = "sha256:571ac1dc6991c450b25a9c2d84a3705e2ae7a53467b5d111c24fa8baabbed320", size = 5303, upload-time = "2025-11-10T22:07:40.673Z" }, +] + [[package]] name = "annotated-types" version = "0.7.0" @@ -176,6 +185,19 @@ version = "4.9.3" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/3e/38/7859ff46355f76f8d19459005ca000b6e7012f2f1ca597746cbcd1fbfe5e/antlr4-python3-runtime-4.9.3.tar.gz", hash = "sha256:f224469b4168294902bb1efa80a8bf7855f24c99aef99cbefc1bcd3cce77881b", size = 117034, upload-time = "2021-11-06T17:52:23.524Z" } +[[package]] +name = "anyio" +version = "4.12.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "idna" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/96/f0/5eb65b2bb0d09ac6776f2eb54adee6abe8228ea05b20a5ad0e4945de8aac/anyio-4.12.1.tar.gz", hash = "sha256:41cfcc3a4c85d3f05c932da7c26d0201ac36f72abd4435ba90d0464a3ffed703", size = 228685, upload-time = "2026-01-06T11:45:21.246Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/38/0e/27be9fdef66e72d64c0cdc3cc2823101b80585f8119b5c112c2e8f5f7dab/anyio-4.12.1-py3-none-any.whl", hash = "sha256:d405828884fc140aa80a3c667b8beed277f1dfedec42ba031bd6ac3db606ab6c", size = 113592, upload-time = "2026-01-06T11:45:19.497Z" }, +] + [[package]] name = "attrs" version = "25.4.0" @@ -747,6 +769,9 @@ docs = [ { name = "mkdocstrings", extra = ["python"] }, { name = "pillow" }, ] +hf = [ + { name = "huggingface-hub" }, +] zarr-conversion = [ { name = "fire" }, { name = "numcodecs" }, @@ -766,6 +791,7 @@ dev = [ requires-dist = [ { name = "cairosvg", marker = "extra == 'docs'", specifier = ">=2.7.1" }, { name = "fire", marker = "extra == 'zarr-conversion'", specifier = ">=0.5.0" }, + { name = "huggingface-hub", marker = "extra == 'hf'", specifier = ">=0.20.0" }, { name = "hydra-core", specifier = ">=1.3.2" }, { name = "lightning", specifier = "~=2.5.6" }, { name = "mkdocs-gen-files", marker = "extra == 'docs'", specifier = ">=0.5.0" }, @@ -789,7 +815,7 @@ requires-dist = [ { name = "zarr", specifier = ">=3.1.3" }, { name = "zarr", marker = "extra == 'zarr-conversion'", specifier = ">=3.1.3" }, ] -provides-extras = ["dev", "docs", "zarr-conversion"] +provides-extras = ["dev", "docs", "hf", "zarr-conversion"] [package.metadata.requires-dev] dev = [ @@ -1035,6 +1061,95 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/2a/b1/9ff6578d789a89812ff21e4e0f80ffae20a65d5dd84e7a17873fe3b365be/griffe-1.14.0-py3-none-any.whl", hash = "sha256:0e9d52832cccf0f7188cfe585ba962d2674b241c01916d780925df34873bceb0", size = 144439, upload-time = "2025-09-05T15:02:27.511Z" }, ] +[[package]] +name = "h11" +version = "0.16.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload-time = "2025-04-24T03:35:25.427Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" }, +] + +[[package]] +name = "hf-xet" +version = "1.4.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/09/08/23c84a26716382c89151b5b447b4beb19e3345f3a93d3b73009a71a57ad3/hf_xet-1.4.2.tar.gz", hash = "sha256:b7457b6b482d9e0743bd116363239b1fa904a5e65deede350fbc0c4ea67c71ea", size = 672357, upload-time = "2026-03-13T06:58:51.077Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/18/06/e8cf74c3c48e5485c7acc5a990d0d8516cdfb5fdf80f799174f1287cc1b5/hf_xet-1.4.2-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:ac8202ae1e664b2c15cdfc7298cbb25e80301ae596d602ef7870099a126fcad4", size = 3796125, upload-time = "2026-03-13T06:58:33.177Z" }, + { url = "https://files.pythonhosted.org/packages/66/d4/b73ebab01cbf60777323b7de9ef05550790451eb5172a220d6b9845385ec/hf_xet-1.4.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:6d2f8ee39fa9fba9af929f8c0d0482f8ee6e209179ad14a909b6ad78ffcb7c81", size = 3555985, upload-time = "2026-03-13T06:58:31.797Z" }, + { url = "https://files.pythonhosted.org/packages/ff/e7/ded6d1bd041c3f2bca9e913a0091adfe32371988e047dd3a68a2463c15a2/hf_xet-1.4.2-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4642a6cf249c09da8c1f87fe50b24b2a3450b235bf8adb55700b52f0ea6e2eb6", size = 4212085, upload-time = "2026-03-13T06:58:24.323Z" }, + { url = "https://files.pythonhosted.org/packages/97/c1/a0a44d1f98934f7bdf17f7a915b934f9fca44bb826628c553589900f6df8/hf_xet-1.4.2-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:769431385e746c92dc05492dde6f687d304584b89c33d79def8367ace06cb555", size = 3988266, upload-time = "2026-03-13T06:58:22.887Z" }, + { url = "https://files.pythonhosted.org/packages/7a/82/be713b439060e7d1f1d93543c8053d4ef2fe7e6922c5b31642eaa26f3c4b/hf_xet-1.4.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:c9dd1c1bc4cc56168f81939b0e05b4c36dd2d28c13dc1364b17af89aa0082496", size = 4188513, upload-time = "2026-03-13T06:58:40.858Z" }, + { url = "https://files.pythonhosted.org/packages/21/a6/cbd4188b22abd80ebd0edbb2b3e87f2633e958983519980815fb8314eae5/hf_xet-1.4.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:fca58a2ae4e6f6755cc971ac6fcdf777ea9284d7e540e350bb000813b9a3008d", size = 4428287, upload-time = "2026-03-13T06:58:42.601Z" }, + { url = "https://files.pythonhosted.org/packages/b2/4e/84e45b25e2e3e903ed3db68d7eafa96dae9a1d1f6d0e7fc85120347a852f/hf_xet-1.4.2-cp313-cp313t-win_amd64.whl", hash = "sha256:163aab46854ccae0ab6a786f8edecbbfbaa38fcaa0184db6feceebf7000c93c0", size = 3665574, upload-time = "2026-03-13T06:58:53.881Z" }, + { url = "https://files.pythonhosted.org/packages/ee/71/c5ac2b9a7ae39c14e91973035286e73911c31980fe44e7b1d03730c00adc/hf_xet-1.4.2-cp313-cp313t-win_arm64.whl", hash = "sha256:09b138422ecbe50fd0c84d4da5ff537d27d487d3607183cd10e3e53f05188e82", size = 3528760, upload-time = "2026-03-13T06:58:52.187Z" }, + { url = "https://files.pythonhosted.org/packages/1e/0f/fcd2504015eab26358d8f0f232a1aed6b8d363a011adef83fe130bff88f7/hf_xet-1.4.2-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:949dcf88b484bb9d9276ca83f6599e4aa03d493c08fc168c124ad10b2e6f75d7", size = 3796493, upload-time = "2026-03-13T06:58:39.267Z" }, + { url = "https://files.pythonhosted.org/packages/82/56/19c25105ff81731ca6d55a188b5de2aa99d7a2644c7aa9de1810d5d3b726/hf_xet-1.4.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:41659966020d59eb9559c57de2cde8128b706a26a64c60f0531fa2318f409418", size = 3555797, upload-time = "2026-03-13T06:58:37.546Z" }, + { url = "https://files.pythonhosted.org/packages/bf/e3/8933c073186849b5e06762aa89847991d913d10a95d1603eb7f2c3834086/hf_xet-1.4.2-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5c588e21d80010119458dd5d02a69093f0d115d84e3467efe71ffb2c67c19146", size = 4212127, upload-time = "2026-03-13T06:58:30.539Z" }, + { url = "https://files.pythonhosted.org/packages/eb/01/f89ebba4e369b4ed699dcb60d3152753870996f41c6d22d3d7cac01310e1/hf_xet-1.4.2-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:a296744d771a8621ad1d50c098d7ab975d599800dae6d48528ba3944e5001ba0", size = 3987788, upload-time = "2026-03-13T06:58:29.139Z" }, + { url = "https://files.pythonhosted.org/packages/84/4d/8a53e5ffbc2cc33bbf755382ac1552c6d9af13f623ed125fe67cc3e6772f/hf_xet-1.4.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:f563f7efe49588b7d0629d18d36f46d1658fe7e08dce3fa3d6526e1c98315e2d", size = 4188315, upload-time = "2026-03-13T06:58:48.017Z" }, + { url = "https://files.pythonhosted.org/packages/d1/b8/b7a1c1b5592254bd67050632ebbc1b42cc48588bf4757cb03c2ef87e704a/hf_xet-1.4.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5b2e0132c56d7ee1bf55bdb638c4b62e7106f6ac74f0b786fed499d5548c5570", size = 4428306, upload-time = "2026-03-13T06:58:49.502Z" }, + { url = "https://files.pythonhosted.org/packages/a0/0c/40779e45b20e11c7c5821a94135e0207080d6b3d76e7b78ccb413c6f839b/hf_xet-1.4.2-cp314-cp314t-win_amd64.whl", hash = "sha256:2f45c712c2fa1215713db10df6ac84b49d0e1c393465440e9cb1de73ecf7bbf6", size = 3665826, upload-time = "2026-03-13T06:58:59.88Z" }, + { url = "https://files.pythonhosted.org/packages/51/4c/e2688c8ad1760d7c30f7c429c79f35f825932581bc7c9ec811436d2f21a0/hf_xet-1.4.2-cp314-cp314t-win_arm64.whl", hash = "sha256:6d53df40616f7168abfccff100d232e9d460583b9d86fa4912c24845f192f2b8", size = 3529113, upload-time = "2026-03-13T06:58:58.491Z" }, + { url = "https://files.pythonhosted.org/packages/b4/86/b40b83a2ff03ef05c4478d2672b1fc2b9683ff870e2b25f4f3af240f2e7b/hf_xet-1.4.2-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:71f02d6e4cdd07f344f6844845d78518cc7186bd2bc52d37c3b73dc26a3b0bc5", size = 3800339, upload-time = "2026-03-13T06:58:36.245Z" }, + { url = "https://files.pythonhosted.org/packages/64/2e/af4475c32b4378b0e92a587adb1aa3ec53e3450fd3e5fe0372a874531c00/hf_xet-1.4.2-cp37-abi3-macosx_11_0_arm64.whl", hash = "sha256:e9b38d876e94d4bdcf650778d6ebbaa791dd28de08db9736c43faff06ede1b5a", size = 3559664, upload-time = "2026-03-13T06:58:34.787Z" }, + { url = "https://files.pythonhosted.org/packages/3c/4c/781267da3188db679e601de18112021a5cb16506fe86b246e22c5401a9c4/hf_xet-1.4.2-cp37-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:77e8c180b7ef12d8a96739a4e1e558847002afe9ea63b6f6358b2271a8bdda1c", size = 4217422, upload-time = "2026-03-13T06:58:27.472Z" }, + { url = "https://files.pythonhosted.org/packages/68/47/d6cf4a39ecf6c7705f887a46f6ef5c8455b44ad9eb0d391aa7e8a2ff7fea/hf_xet-1.4.2-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:c3b3c6a882016b94b6c210957502ff7877802d0dbda8ad142c8595db8b944271", size = 3992847, upload-time = "2026-03-13T06:58:25.989Z" }, + { url = "https://files.pythonhosted.org/packages/2d/ef/e80815061abff54697239803948abc665c6b1d237102c174f4f7a9a5ffc5/hf_xet-1.4.2-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:9d9a634cc929cfbaf2e1a50c0e532ae8c78fa98618426769480c58501e8c8ac2", size = 4193843, upload-time = "2026-03-13T06:58:44.59Z" }, + { url = "https://files.pythonhosted.org/packages/54/75/07f6aa680575d9646c4167db6407c41340cbe2357f5654c4e72a1b01ca14/hf_xet-1.4.2-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:6b0932eb8b10317ea78b7da6bab172b17be03bbcd7809383d8d5abd6a2233e04", size = 4432751, upload-time = "2026-03-13T06:58:46.533Z" }, + { url = "https://files.pythonhosted.org/packages/cd/71/193eabd7e7d4b903c4aa983a215509c6114915a5a237525ec562baddb868/hf_xet-1.4.2-cp37-abi3-win_amd64.whl", hash = "sha256:ad185719fb2e8ac26f88c8100562dbf9dbdcc3d9d2add00faa94b5f106aea53f", size = 3671149, upload-time = "2026-03-13T06:58:57.07Z" }, + { url = "https://files.pythonhosted.org/packages/b4/7e/ccf239da366b37ba7f0b36095450efae4a64980bdc7ec2f51354205fdf39/hf_xet-1.4.2-cp37-abi3-win_arm64.whl", hash = "sha256:32c012286b581f783653e718c1862aea5b9eb140631685bb0c5e7012c8719a87", size = 3533426, upload-time = "2026-03-13T06:58:55.46Z" }, +] + +[[package]] +name = "httpcore" +version = "1.0.9" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "h11" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484, upload-time = "2025-04-24T22:06:22.219Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" }, +] + +[[package]] +name = "httpx" +version = "0.28.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "certifi" }, + { name = "httpcore" }, + { name = "idna" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406, upload-time = "2024-12-06T15:37:23.222Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" }, +] + +[[package]] +name = "huggingface-hub" +version = "1.7.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "filelock" }, + { name = "fsspec" }, + { name = "hf-xet", marker = "platform_machine == 'AMD64' or platform_machine == 'aarch64' or platform_machine == 'amd64' or platform_machine == 'arm64' or platform_machine == 'x86_64'" }, + { name = "httpx" }, + { name = "packaging" }, + { name = "pyyaml" }, + { name = "tqdm" }, + { name = "typer" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b4/a8/94ccc0aec97b996a3a68f3e1fa06a4bd7185dd02bf22bfba794a0ade8440/huggingface_hub-1.7.1.tar.gz", hash = "sha256:be38fe66e9b03c027ad755cb9e4b87ff0303c98acf515b5d579690beb0bf3048", size = 722097, upload-time = "2026-03-13T09:36:07.758Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6f/75/ca21955d6117a394a482c7862ce96216239d0e3a53133ae8510727a8bcfa/huggingface_hub-1.7.1-py3-none-any.whl", hash = "sha256:38c6cce7419bbde8caac26a45ed22b0cea24152a8961565d70ec21f88752bfaa", size = 616308, upload-time = "2026-03-13T09:36:06.062Z" }, +] + [[package]] name = "hydra-core" version = "1.3.2" @@ -1230,6 +1345,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/70/ae/44c4a6a4cbb496d93c6257954260fe3a6e91b7bed2240e5dad2a717f5111/markdown-3.9-py3-none-any.whl", hash = "sha256:9f4d91ed810864ea88a6f32c07ba8bee1346c0cc1f6b1f9f6c822f2a9667d280", size = 107441, upload-time = "2025-09-04T20:25:21.784Z" }, ] +[[package]] +name = "markdown-it-py" +version = "4.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mdurl" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5b/f5/4ec618ed16cc4f8fb3b701563655a69816155e79e24a17b651541804721d/markdown_it_py-4.0.0.tar.gz", hash = "sha256:cb0a2b4aa34f932c007117b194e945bd74e0ec24133ceb5bac59009cda1cb9f3", size = 73070, upload-time = "2025-08-11T12:57:52.854Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/94/54/e7d793b573f298e1c9013b8c4dade17d481164aa517d1d7148619c2cedbf/markdown_it_py-4.0.0-py3-none-any.whl", hash = "sha256:87327c59b172c5011896038353a81343b6754500a08cd7a4973bb48c6d578147", size = 87321, upload-time = "2025-08-11T12:57:51.923Z" }, +] + [[package]] name = "markupsafe" version = "3.0.3" @@ -1368,6 +1495,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/9a/cc/3fe688ff1355010937713164caacf9ed443675ac48a997bab6ed23b3f7c0/matplotlib-3.10.7-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3886e47f64611046bc1db523a09dd0a0a6bed6081e6f90e13806dd1d1d1b5e91", size = 8693919, upload-time = "2025-10-09T00:27:58.41Z" }, ] +[[package]] +name = "mdurl" +version = "0.1.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729, upload-time = "2022-08-14T12:40:10.846Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979, upload-time = "2022-08-14T12:40:09.779Z" }, +] + [[package]] name = "mergedeep" version = "1.3.4" @@ -2677,6 +2813,19 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6", size = 64738, upload-time = "2025-08-18T20:46:00.542Z" }, ] +[[package]] +name = "rich" +version = "14.3.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markdown-it-py" }, + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b3/c6/f3b320c27991c46f43ee9d856302c70dc2d0fb2dba4842ff739d5f46b393/rich-14.3.3.tar.gz", hash = "sha256:b8daa0b9e4eef54dd8cf7c86c03713f53241884e814f4e2f5fb342fe520f639b", size = 230582, upload-time = "2026-02-19T17:23:12.474Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/14/25/b208c5683343959b670dc001595f2f3737e051da617f66c31f7c4fa93abc/rich-14.3.3-py3-none-any.whl", hash = "sha256:793431c1f8619afa7d3b52b2cdec859562b950ea0d4b6b505397612db8d5362d", size = 310458, upload-time = "2026-02-19T17:23:13.732Z" }, +] + [[package]] name = "ruamel-yaml" version = "0.18.16" @@ -2920,6 +3069,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/a3/dc/17031897dae0efacfea57dfd3a82fdd2a2aeb58e0ff71b77b87e44edc772/setuptools-80.9.0-py3-none-any.whl", hash = "sha256:062d34222ad13e0cc312a4c02d73f059e86a4acbfbdea8f8f76b28c99f306922", size = 1201486, upload-time = "2025-05-27T00:56:49.664Z" }, ] +[[package]] +name = "shellingham" +version = "1.5.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/58/15/8b3609fd3830ef7b27b655beb4b4e9c62313a4e8da8c676e142cc210d58e/shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de", size = 10310, upload-time = "2023-10-24T04:13:40.426Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686", size = 9755, upload-time = "2023-10-24T04:13:38.866Z" }, +] + [[package]] name = "six" version = "1.17.0" @@ -3197,6 +3355,21 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/16/b5/b0d3d8b901b6a04ca38df5e24c27e53afb15b93624d7fd7d658c7cd9352a/triton-3.5.1-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bac7f7d959ad0f48c0e97d6643a1cc0fd5786fe61cb1f83b537c6b2d54776478", size = 170582192, upload-time = "2025-11-11T17:41:23.963Z" }, ] +[[package]] +name = "typer" +version = "0.24.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "annotated-doc" }, + { name = "click" }, + { name = "rich" }, + { name = "shellingham" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f5/24/cb09efec5cc954f7f9b930bf8279447d24618bb6758d4f6adf2574c41780/typer-0.24.1.tar.gz", hash = "sha256:e39b4732d65fbdcde189ae76cf7cd48aeae72919dea1fdfc16593be016256b45", size = 118613, upload-time = "2026-02-21T16:54:40.609Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4a/91/48db081e7a63bb37284f9fbcefda7c44c277b18b0e13fbc36ea2335b71e6/typer-0.24.1-py3-none-any.whl", hash = "sha256:112c1f0ce578bfb4cab9ffdabc68f031416ebcc216536611ba21f04e9aa84c9e", size = 56085, upload-time = "2026-02-21T16:54:41.616Z" }, +] + [[package]] name = "typing-extensions" version = "4.15.0"