-
Notifications
You must be signed in to change notification settings - Fork 16
[codex] Trigger Hugging Face access during inference #13
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,14 +1,16 @@ | ||
| { | ||
| "input_dir": "@bundle_root", | ||
| "input_suffix": "*.nii.gz", | ||
| "input_root_abs": "$os.path.abspath(@input_dir)", | ||
| "input_root_abs": "$os.path.realpath(@input_dir)", | ||
| "output_dir_abs": "$os.path.realpath(@output_dir)", | ||
| "batch_skip_dir_names": [], | ||
| "batch_skip_dir_prefixes": [], | ||
| "batch_resume_skip_existing": true, | ||
| "batch_use_input_list_cache": true, | ||
| "batch_cache_wait_sec": 120, | ||
| "input_list": "$scripts.batch_inference_utils.build_input_list(os.path.abspath(@input_dir), os.path.abspath(@output_dir), @output_postfix, @output_ext, @batch_skip_dir_names, @batch_skip_dir_prefixes, @batch_resume_skip_existing, @batch_use_input_list_cache, @batch_cache_wait_sec)", | ||
| "input_list": "$scripts.batch_inference_utils.build_input_list(@input_root_abs, @output_dir_abs, @output_postfix, @output_ext, @batch_skip_dir_names, @batch_skip_dir_prefixes, @batch_resume_skip_existing, @batch_use_input_list_cache, @batch_cache_wait_sec)", | ||
| "input_dicts": "$[{'image': x, 'label_prompt': @everything_labels} for x in @input_list]", | ||
| "dataset#data": "@input_dicts", | ||
| "postprocessing#transforms#4#output_dir": "@output_dir_abs", | ||
| "postprocessing#transforms#4#data_root_dir": "@input_root_abs" | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -16,4 +16,4 @@ timm | |
| pytorch-ignite | ||
| tensorboardX | ||
| mlflow | ||
| huggingface_hub | ||
| huggingface_hub | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,85 @@ | ||
| import os | ||
| import shutil | ||
| from pathlib import Path | ||
|
|
||
|
|
||
| def _is_rank_zero() -> bool: | ||
| for name in ("RANK", "LOCAL_RANK", "SLURM_PROCID"): | ||
| value = os.environ.get(name) | ||
| if value not in (None, "", "0"): | ||
| return False | ||
| return True | ||
|
|
||
|
|
||
| def touch_huggingface_download_counter( | ||
| repo_id: str, | ||
| filename: str = "config.json", | ||
| revision: str = "main", | ||
| rank_zero_only: bool = True, | ||
| ) -> str | None: | ||
| """Force a tiny Hugging Face file request without re-downloading weights.""" | ||
|
|
||
| if rank_zero_only and not _is_rank_zero(): | ||
| return None | ||
|
|
||
| try: | ||
| from huggingface_hub import hf_hub_download | ||
| except ImportError: | ||
| print("[nvseg] warning: huggingface_hub is not installed; skipping Hugging Face download counter touch.") | ||
| return None | ||
|
|
||
| try: | ||
| path = hf_hub_download( | ||
| repo_id=repo_id, | ||
| filename=filename, | ||
| repo_type="model", | ||
| revision=revision, | ||
| force_download=True, | ||
| ) | ||
| except Exception as exc: # noqa: BLE001 | ||
| print(f"[nvseg] warning: could not touch Hugging Face download counter for {repo_id}/{filename}: {exc}") | ||
| return None | ||
|
|
||
| print(f"[nvseg] touched Hugging Face download counter for {repo_id}/{filename}") | ||
| return path | ||
|
|
||
|
|
||
| def prepare_huggingface_checkpoint( | ||
| repo_id: str, | ||
| checkpoint_filename: str, | ||
| local_checkpoint_path: str, | ||
| counter_filename: str = "config.json", | ||
| revision: str = "main", | ||
| rank_zero_only: bool = True, | ||
| ) -> str: | ||
| """Ensure the local MONAI checkpoint path exists and touch HF stats for this inference.""" | ||
|
|
||
| local_path = Path(local_checkpoint_path) | ||
|
|
||
| if rank_zero_only and not _is_rank_zero(): | ||
| return str(local_path) | ||
|
|
||
| touch_huggingface_download_counter(repo_id, counter_filename, revision, rank_zero_only=False) | ||
| if local_path.exists(): | ||
| return str(local_path) | ||
|
|
||
| try: | ||
| from huggingface_hub import hf_hub_download | ||
| except ImportError as exc: | ||
| raise RuntimeError(f"{local_path} does not exist and huggingface_hub is not installed; cannot download {repo_id}.") from exc | ||
|
|
||
| checkpoint_path = hf_hub_download( | ||
| repo_id=repo_id, | ||
| filename=checkpoint_filename, | ||
| repo_type="model", | ||
| revision=revision, | ||
| ) | ||
|
|
||
| local_path.parent.mkdir(parents=True, exist_ok=True) | ||
| try: | ||
| local_path.symlink_to(checkpoint_path) | ||
| except OSError: | ||
| shutil.copy2(checkpoint_path, local_path) | ||
|
Comment on lines
+79
to
+82
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
If the HF cache has been cleaned up (e.g. Add |
||
|
|
||
| print(f"[nvseg] prepared checkpoint at {local_path}") | ||
| return str(local_path) | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,41 @@ | ||
| #!/usr/bin/env bash | ||
| set -euo pipefail | ||
|
|
||
| SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) | ||
| BUNDLE_ROOT=$(cd "${SCRIPT_DIR}/.." && pwd) | ||
| PYTHON_BIN=${PYTHON_BIN:-python} | ||
| WORK_DIR=${WORK_DIR:-$(mktemp -d)} | ||
| KEEP_TEST_WORKDIR=${KEEP_TEST_WORKDIR:-0} | ||
|
|
||
| if [[ "${KEEP_TEST_WORKDIR}" != "1" ]]; then | ||
| trap 'rm -rf "${WORK_DIR}"' EXIT | ||
| fi | ||
|
|
||
| INPUT_DIR="${WORK_DIR}/input" | ||
| OUTPUT_DIR="${WORK_DIR}/output" | ||
| mkdir -p "${INPUT_DIR}" "${OUTPUT_DIR}" | ||
|
|
||
| cp "${BUNDLE_ROOT}/example/spleen_03.nii.gz" "${INPUT_DIR}/" | ||
|
|
||
| cd "${BUNDLE_ROOT}" | ||
|
|
||
| "${PYTHON_BIN}" -m monai.bundle run \ | ||
| --config_file="['configs/inference.json', 'configs/batch_inference.json']" \ | ||
| --input_dir="${INPUT_DIR}" \ | ||
| --output_dir="${OUTPUT_DIR}" \ | ||
| 2>&1 | tee "${WORK_DIR}/batch_inference.log" | ||
|
|
||
| EXPECTED_OUTPUT="${OUTPUT_DIR}/spleen_03/spleen_03_trans.nii.gz" | ||
| test -s "${EXPECTED_OUTPUT}" | ||
| grep -q "\[nvseg\] batch resume (skip existing outputs): 1 volume" "${WORK_DIR}/batch_inference.log" | ||
| grep -q "\[nvseg\] touched Hugging Face download counter for nvidia/NV-Segment-CT/config.json" "${WORK_DIR}/batch_inference.log" | ||
|
|
||
| "${PYTHON_BIN}" -m monai.bundle run \ | ||
| --config_file="['configs/inference.json', 'configs/batch_inference.json']" \ | ||
| --input_dir="${INPUT_DIR}" \ | ||
| --output_dir="${OUTPUT_DIR}" \ | ||
| 2>&1 | tee "${WORK_DIR}/batch_resume.log" | ||
|
|
||
| grep -q "\[nvseg\] batch: nothing to run (resume); ok" "${WORK_DIR}/batch_resume.log" | ||
|
|
||
| echo "[nvseg-test] CT batch inference smoke test passed: ${EXPECTED_OUTPUT}" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
_is_rank_zeroreturnsFalseif any ofRANK,LOCAL_RANK, orSLURM_PROCIDis not in(None, "", "0"). In a multi-node SLURM launch that wrapstorchrun, it is possible forSLURM_PROCIDto be set to the SLURM task ID whileRANK/LOCAL_RANKare also set. If those values mismatch (e.g., SLURM assigns non-zeroSLURM_PROCIDto the PyTorch rank-0 process), the function incorrectly returnsFalsefor rank 0, meaning no process performs the HF download, and the barrier inmgpu_inference.jsonproceeds withmodels/model.ptstill missing, causing all ranks to crash atCheckpointLoader. The same logic is duplicated in the CTMR variant.