Source code for medvision_bm.utils.install_utils

"""Installation and environment-setup helpers for the MedVision benchmark.

This module bundles the routines used to provision a runtime for the benchmark:
installing the vendored ``lmms-eval`` package and the ``medvision_ds`` dataset
codebase, and configuring the environment variables that point Hugging Face and
the MedVision dataset loader at a chosen data directory. It also provides
convenience installers for CUDA, PyTorch, Flash-Attention, and vLLM used when
setting up per-model conda environments.
"""

import os
import shlex
import subprocess
import sys
from importlib.resources import files  # Python 3.9+
from pathlib import Path

from huggingface_hub import snapshot_download


[docs] def run_pip_install(requirements_path): # Normalize input to a Path so both str and Path work. req_path = Path(requirements_path).expanduser().resolve(strict=False) if not req_path.exists() or not req_path.is_file(): raise FileNotFoundError(f"Requirements file not found: {requirements_path}") # Use the current interpreter to run pip to avoid PATH/env mismatches. cmd = [ sys.executable, "-m", "pip", "install", "--upgrade", "--force-reinstall", "--no-deps", "-r", str(req_path), ] env = os.environ.copy() env.setdefault("PIP_DISABLE_PIP_VERSION_CHECK", "1") print(f"Installing packages from: {req_path}") subprocess.run(cmd, env=env, check=True)
[docs] def ensure_hf_hub_installed(hf_hub_version="0.35.3"): try: from huggingface_hub import snapshot_download # noqa: F401 except ImportError: subprocess.run( f"pip install huggingface_hub[cli]=={hf_hub_version}", check=True, shell=True, )
def _install_lmms_eval( lmms_eval_dir, editable_install=False, proj_dependency=None, ): # compose extras text like .[extra] extras_txt = f"[{proj_dependency}]" if proj_dependency else "" tmp_build_lock_file = os.path.join(lmms_eval_dir, ".build.lock") build_dir = os.path.join(lmms_eval_dir, "build") dist_dir = os.path.join(lmms_eval_dir, "dist") egg_info_dir = os.path.join(lmms_eval_dir, "lmms_eval.egg-info") wheel_dir = os.path.join(lmms_eval_dir, "wheels") os.makedirs(wheel_dir, exist_ok=True) # Common pip flags base_pip_flags = "--no-cache-dir --force-reinstall" # Case A: Editable install (always from source; extras allowed) if editable_install: # Example: pip install -e .[qwen2_5_vl] cmd = ( f"flock -w 600 {shlex.quote(tmp_build_lock_file)} bash -lc '" f"python -m pip install {base_pip_flags} -e .{extras_txt}" f"'" ) subprocess.run(cmd, check=True, shell=True, cwd=lmms_eval_dir) return # Case B: Non-editable, NO extras → build wheel once, then install wheel if proj_dependency is None: cmd = ( f"flock -w 600 {shlex.quote(tmp_build_lock_file)} bash -lc '" f"rm -rf {shlex.quote(build_dir)} {shlex.quote(dist_dir)} {shlex.quote(egg_info_dir)} && " f"rm -f {shlex.quote(wheel_dir)}/*.whl && " f"python -m pip install --upgrade build && " f"python -m build --wheel --outdir {shlex.quote(wheel_dir)} {shlex.quote(lmms_eval_dir)} && " f"latest_wheel=$(ls -t {shlex.quote(wheel_dir)}/lmms_eval-*.whl | head -n1) && " f'pip install {base_pip_flags} "$latest_wheel"' f"'" ) subprocess.run(cmd, check=True, shell=True, cwd=lmms_eval_dir) return # Case C: Non-editable WITH extras → install from source with extras # (extras on a wheel path is not supported) cmd = ( f"flock -w 600 {shlex.quote(tmp_build_lock_file)} bash -lc '" f"python -m pip install {base_pip_flags} .{extras_txt}" f"'" ) subprocess.run(cmd, check=True, shell=True, cwd=lmms_eval_dir) def install_lmms_eval( benchmark_dir, lmms_eval_folder, editable_install=False, proj_dependency=None, ): lmms_eval_dir = os.path.join(benchmark_dir, lmms_eval_folder) _install_lmms_eval( lmms_eval_dir=lmms_eval_dir, editable_install=editable_install, proj_dependency=proj_dependency, )
[docs] def install_vendored_lmms_eval( editable_install=True, proj_dependency=None, ): """Install the vendored ``lmms-eval`` package that ships inside ``medvision_bm``. Locates the ``medvision_lmms_eval`` package shipped as package data and installs it via the shared installer. Editable installation is used by default because the task definition files are otherwise not discovered. Args: editable_install (bool): Install in editable (``pip install -e``) mode. Defaults to ``True``. proj_dependency (str, optional): Name of an optional-dependency extra to install (rendered as ``.[extra]``). Defaults to ``None``. """ # Locate the vendored lmms-eval package, check [tool.setuptools.package-data] in pyproject.toml lmms_eval_dir = str(files("medvision_bm").joinpath("medvision_lmms_eval")) # NOTE: Must install the vendored lmms-eval in editable mode, otherwise tasks files won't be found. # TODO: Check: Why editable install causes issues in some cases? _install_lmms_eval( lmms_eval_dir=lmms_eval_dir, editable_install=editable_install, proj_dependency=proj_dependency, )
[docs] def setup_env_hf(data_dir): """Point Hugging Face's cache and dataset cache at ``data_dir``. Sets ``HF_HOME`` and ``HF_DATASETS_CACHE`` to subdirectories of ``data_dir`` so that models and datasets are cached under the benchmark's data directory. Args: data_dir (str): Data directory; a relative path is resolved to an absolute path. """ # Safeguard data_dir: you can use relative path with this function data_dir = os.path.abspath(data_dir) # Set Hugging Face dataset and cache directories os.environ["HF_DATASETS_CACHE"] = os.path.join( data_dir, ".cache", "huggingface", "datasets" ) os.environ["HF_HOME"] = os.path.join(data_dir, ".cache", "huggingface")
[docs] def setup_env_medvision_ds( data_dir, force_install_code=True, force_download_data=False, ): """Set the environment variables that configure the ``medvision_ds`` loader. Sets ``MedVision_DATA_DIR`` to ``data_dir`` (creating the directory if needed) and, when requested, the force-install and force-download flags read by the dataset codebase. The flags are only set when their argument is ``True``; they are left unchanged otherwise. Args: data_dir (str): Data directory; a relative path is resolved to an absolute path. force_install_code (bool): When ``True``, set ``MedVision_FORCE_INSTALL_CODE=true`` to force reinstallation of the dataset codebase. Defaults to ``True``. force_download_data (bool): When ``True``, set ``MedVision_FORCE_DOWNLOAD_DATA=true`` to force re-download of the data. Defaults to ``False``. """ # Safeguard data_dir: you can use relative path with this function data_dir = os.path.abspath(data_dir) # Set dataset directory os.makedirs(data_dir, exist_ok=True) os.environ["MedVision_DATA_DIR"] = data_dir # Force install dataset codebase, default to "False" if force_install_code: os.environ["MedVision_FORCE_INSTALL_CODE"] = "true" # Force download dataset, default to "False" if force_download_data: os.environ["MedVision_FORCE_DOWNLOAD_DATA"] = "true"
[docs] def setup_env_hf_medvision_ds( data_dir, force_install_code=True, force_download_data=False, ): """Configure both the ``medvision_ds`` and Hugging Face environment variables. Calls ``setup_env_medvision_ds`` followed by ``setup_env_hf`` for the same data directory. Args: data_dir (str): Data directory; a relative path is resolved to an absolute path. force_install_code (bool): Forwarded to ``setup_env_medvision_ds``. Defaults to ``True``. force_download_data (bool): Forwarded to ``setup_env_medvision_ds``. Defaults to ``False``. """ # Set environment variables for medvision_ds setup_env_medvision_ds( data_dir=data_dir, force_install_code=force_install_code, force_download_data=force_download_data, ) # Set environment variables for Hugging Face setup_env_hf(data_dir)
[docs] def install_medvision_ds( data_dir, local_dir=None, ): """Build and install the ``medvision_ds`` dataset codebase from source. When ``local_dir`` is ``None``, downloads the dataset's ``src/`` directory from the ``YongchengYAO/MedVision`` Hugging Face dataset repo into ``data_dir``; otherwise uses ``src/`` under ``local_dir``. A wheel is built from that source (guarded by a ``flock`` build lock, with a no-lock fallback) and installed with ``--force-reinstall``. Finally, the Hugging Face and ``medvision_ds`` environment variables are configured for ``data_dir``. Args: data_dir (str): Data directory used for the snapshot download and for environment setup; a relative path is resolved to an absolute path. local_dir (str, optional): Directory containing an existing ``src/`` tree to install from. When provided, no download is performed. Defaults to ``None``. """ if local_dir is None: # Safeguard data_dir: you can use relative path with this function data_dir = os.path.abspath(data_dir) os.makedirs(data_dir, exist_ok=True) snapshot_download( repo_id="YongchengYAO/MedVision", allow_patterns="src/*", repo_type="dataset", local_dir=data_dir, ) dir_bmvqa = os.path.abspath(os.path.join(data_dir, "src")) else: dir_bmvqa = os.path.abspath(os.path.join(local_dir, "src")) tmp_build_lock_file = os.path.join(dir_bmvqa, ".build.lock") build_dir = os.path.join(dir_bmvqa, "build") dist_dir = os.path.join(dir_bmvqa, "dist") egg_info_dir = os.path.join(dir_bmvqa, "medvision_ds.egg-info") wheel_dir = os.path.join(dir_bmvqa, "wheels") os.makedirs(wheel_dir, exist_ok=True) cmd_w_flock = ( f"flock -w 600 {shlex.quote(tmp_build_lock_file)} bash -lc '" f"rm -rf {shlex.quote(build_dir)} {shlex.quote(dist_dir)} {shlex.quote(egg_info_dir)} && " f"rm -f {shlex.quote(wheel_dir)}/*.whl && " f"python -m pip install --upgrade build && " f"python -m build --wheel --outdir {shlex.quote(wheel_dir)} {shlex.quote(dir_bmvqa)} && " f"latest_wheel=$(ls -t {shlex.quote(wheel_dir)}/medvision_ds-*.whl | head -n1) && " f'pip install --no-cache-dir --force-reinstall "$latest_wheel"\'' ) # Try with flock, fallback to without flock if it fails try: subprocess.run(cmd_w_flock, check=True, shell=True) except subprocess.CalledProcessError: print("Warning: flock failed, attempting installation without file lock...") cmd_no_flock = ( f"bash -lc '" f"rm -rf {shlex.quote(build_dir)} {shlex.quote(dist_dir)} {shlex.quote(egg_info_dir)} && " f"rm -f {shlex.quote(wheel_dir)}/*.whl && " f"python -m pip install --upgrade build && " f"python -m build --wheel --outdir {shlex.quote(wheel_dir)} {shlex.quote(dir_bmvqa)} && " f"latest_wheel=$(ls -t {shlex.quote(wheel_dir)}/medvision_ds-*.whl | head -n1) && " f'pip install --no-cache-dir --force-reinstall "$latest_wheel"\'' ) subprocess.run(cmd_no_flock, check=True, shell=True) # Set environment variables for medvision_ds setup_env_hf_medvision_ds(data_dir=data_dir)
def pip_install_medvision_ds(): try: print( '\n[Info] Installing medvision_ds from Hugging Face Datasets repo: pip install "git+https://huggingface.co/datasets/YongchengYAO/MedVision.git#subdirectory=src"' ) subprocess.run( 'pip install "git+https://huggingface.co/datasets/YongchengYAO/MedVision.git#subdirectory=src"', check=True, shell=True, ) print("Successfully installed medvision_ds.") except subprocess.CalledProcessError as e: print(f"Error installing medvision_ds: {e}", file=sys.stderr) def pip_install_medvision_bm(): try: print( '\n[Info] Installing medvision_bm from GitHub repo: pip install "git+https://github.com/YongchengYAO/MedVision.git"' ) subprocess.run( 'pip install "git+https://github.com/YongchengYAO/MedVision.git"', check=True, shell=True, ) print("Successfully installed medvision_bm.") except subprocess.CalledProcessError as e: print(f"Error installing medvision_bm: {e}", file=sys.stderr)
[docs] def setup_env_cuda(): print("Setting up CUDA environment...") cuda_home = os.environ.get("CONDA_PREFIX", "") os.environ["CUDA_HOME"] = cuda_home os.environ["PATH"] = f"{cuda_home}/bin:{os.environ.get('PATH', '')}" os.environ["LD_LIBRARY_PATH"] = ( f"{cuda_home}/lib64:{os.environ.get('LD_LIBRARY_PATH', '')}" ) os.environ["LD_LIBRARY_PATH"] = ( f"{cuda_home}/lib:{os.environ.get('LD_LIBRARY_PATH', '')}" )
[docs] def install_cuda_toolkit(version="12.4"): """Install CUDA toolkit using conda.""" print("Installing CUDA toolkit...") subprocess.run( ["conda", "install", "-c", "nvidia", f"cuda-toolkit={version}", "-y"], check=True, ) setup_env_cuda()
def install_torch_cu121(): """Install PyTorch with CUDA support.""" print("Installing PyTorch...") subprocess.run( [ sys.executable, "-m", "pip", "install", "torch==2.5.0+cu121", "torchvision==0.20.0+cu121", "torchaudio==2.5.0+cu121", "--index-url", "https://download.pytorch.org/whl/cu121", "--force-reinstall", ], check=True, ) setup_env_cuda()
[docs] def install_torch_cu124(): """Install PyTorch with CUDA support.""" print("Installing PyTorch...") subprocess.run( [ sys.executable, "-m", "pip", "install", "torch==2.6.0+cu124", "torchvision==0.21.0+cu124", "torchaudio==2.6.0+cu124", "--index-url", "https://download.pytorch.org/whl/cu124", "--force-reinstall", ], check=True, ) setup_env_cuda()
def install_flash_attention_torch_and_deps_py39(): # Install PyTorch with CUDA support print("Installing PyTorch with CUDA 12.4...") subprocess.run( "pip install torch==2.6.0+cu124 torchvision==0.21.0+cu124 torchaudio==2.6.0+cu124 " "--index-url https://download.pytorch.org/whl/cu124 --force-reinstall", check=True, shell=True, ) # Install CUDA print("Installing CUDA toolkit and components...") subprocess.run( "conda install nvidia/label/cuda-12.4.0::cuda-toolkit -y", check=True, shell=True, ) subprocess.run( "conda install nvidia/label/cuda-12.4.0::cuda-nvcc -y", check=True, shell=True ) subprocess.run( "conda install cudnn -y", check=True, shell=True, ) subprocess.run( "pip install --upgrade nvidia-cuda-cupti-cu12==12.4.* " "nvidia-cuda-nvrtc-cu12==12.4.* " "nvidia-cuda-runtime-cu12==12.4.*", check=True, shell=True, ) setup_env_cuda() # Install Flash Attention print("Installing Flash Attention...") subprocess.run( "pip install https://github.com/Dao-AILab/flash-attention/releases/download/v2.7.3/flash_attn-2.7.3+cu12torch2.6cxx11abiFALSE-cp39-cp39-linux_x86_64.whl", check=True, shell=True, ) # Install numpy version 1.26.4 print("Installing numpy...") subprocess.run("pip install numpy==1.26.4", check=True, shell=True) # Install protobuf version 3.20 print("Installing protobuf 3.20.x") subprocess.run("pip install protobuf==3.20", check=True, shell=True)
[docs] def install_flash_attention_torch_and_deps_py39_v2(): # Install PyTorch with CUDA support print("Installing PyTorch with CUDA 12.4...") install_torch_cu124() # Install Flash Attention print("Installing Flash Attention...") subprocess.run( "pip install https://github.com/Dao-AILab/flash-attention/releases/download/v2.7.3/flash_attn-2.7.3+cu12torch2.6cxx11abiFALSE-cp39-cp39-linux_x86_64.whl", check=True, shell=True, ) # Install numpy version 1.26.4 print("Installing numpy...") subprocess.run("pip install numpy==1.26.4", check=True, shell=True) # Install protobuf version 3.20 print("Installing protobuf 3.20.x") subprocess.run("pip install protobuf==3.20", check=True, shell=True)
def install_flash_attention_torch_and_deps_py310(): # Install PyTorch with CUDA support print("Installing PyTorch with CUDA 12.4...") subprocess.run( "pip install torch==2.6.0+cu124 torchvision==0.21.0+cu124 torchaudio==2.6.0+cu124 " "--index-url https://download.pytorch.org/whl/cu124 --force-reinstall", check=True, shell=True, ) # Install CUDA print("Installing CUDA toolkit and components...") subprocess.run( "conda install nvidia/label/cuda-12.4.0::cuda-toolkit -y", check=True, shell=True, ) subprocess.run( "conda install nvidia/label/cuda-12.4.0::cuda-nvcc -y", check=True, shell=True ) subprocess.run( "conda install cudnn -y", check=True, shell=True, ) subprocess.run( "pip install --upgrade nvidia-cuda-cupti-cu12==12.4.* " "nvidia-cuda-nvrtc-cu12==12.4.* " "nvidia-cuda-runtime-cu12==12.4.*", check=True, shell=True, ) setup_env_cuda() # Install Flash Attention print("Installing Flash Attention...") subprocess.run( "pip install https://github.com/Dao-AILab/flash-attention/releases/download/v2.7.3/flash_attn-2.7.3+cu12torch2.6cxx11abiFALSE-cp310-cp310-linux_x86_64.whl", check=True, shell=True, ) # Install numpy version 1.26.4 print("Installing numpy...") subprocess.run("pip install numpy==1.26.4", check=True, shell=True) # Install protobuf version 3.20 print("Installing protobuf 3.20.x") subprocess.run("pip install protobuf==3.20", check=True, shell=True)
[docs] def install_flash_attention_torch_and_deps_py310_v2(): # Install PyTorch with CUDA support print("Installing PyTorch with CUDA 12.4...") install_torch_cu124() # Install Flash Attention print("Installing Flash Attention...") subprocess.run( "pip install https://github.com/Dao-AILab/flash-attention/releases/download/v2.7.3/flash_attn-2.7.3+cu12torch2.6cxx11abiFALSE-cp310-cp310-linux_x86_64.whl", check=True, shell=True, ) # Install numpy version 1.26.4 print("Installing numpy...") subprocess.run("pip install numpy==1.26.4", check=True, shell=True) # Install protobuf version 3.20 print("Installing protobuf 3.20.x") subprocess.run("pip install protobuf==3.20", check=True, shell=True)
def install_flash_attention_torch_and_deps_py311(): # Install PyTorch with CUDA support print("Installing PyTorch with CUDA 12.4...") subprocess.run( "pip install torch==2.6.0+cu124 torchvision==0.21.0+cu124 torchaudio==2.6.0+cu124 " "--index-url https://download.pytorch.org/whl/cu124 --force-reinstall", check=True, shell=True, ) # Install CUDA print("Installing CUDA toolkit and components...") subprocess.run( "conda install nvidia/label/cuda-12.4.0::cuda-toolkit -y", check=True, shell=True, ) subprocess.run( "conda install nvidia/label/cuda-12.4.0::cuda-nvcc -y", check=True, shell=True ) subprocess.run( "conda install cudnn -y", check=True, shell=True, ) subprocess.run( "pip install --upgrade nvidia-cuda-cupti-cu12==12.4.* " "nvidia-cuda-nvrtc-cu12==12.4.* " "nvidia-cuda-runtime-cu12==12.4.*", check=True, shell=True, ) setup_env_cuda() # Install Flash Attention print("Installing Flash Attention...") subprocess.run( "pip install https://github.com/Dao-AILab/flash-attention/releases/download/v2.7.3/flash_attn-2.7.3+cu12torch2.6cxx11abiFALSE-cp311-cp311-linux_x86_64.whl", check=True, shell=True, ) # Install numpy version 1.26.4 print("Installing numpy...") subprocess.run("pip install numpy==1.26.4", check=True, shell=True) # Install protobuf version 3.20 print("Installing protobuf 3.20.x") subprocess.run("pip install protobuf==3.20", check=True, shell=True)
[docs] def install_flash_attention_torch_and_deps_py311_v2(): # Install PyTorch with CUDA support print("Installing PyTorch with CUDA 12.4...") install_torch_cu124() # Install Flash Attention print("Installing Flash Attention...") subprocess.run( "pip install https://github.com/Dao-AILab/flash-attention/releases/download/v2.7.3/flash_attn-2.7.3+cu12torch2.6cxx11abiFALSE-cp311-cp311-linux_x86_64.whl", check=True, shell=True, ) # Install numpy version 1.26.4 print("Installing numpy...") subprocess.run("pip install numpy==1.26.4", check=True, shell=True) # Install protobuf version 3.20 print("Installing protobuf 3.20.x") subprocess.run("pip install protobuf==3.20", check=True, shell=True)
[docs] def setup_env_vllm(data_dir): # Safeguard data_dir: you can use relative path with this function data_dir = os.path.abspath(data_dir) # Ensure proper process spawning os.environ["VLLM_WORKER_MULTIPROC_METHOD"] = "spawn" # Set the cache directory for vllm os.environ["XDG_CACHE_HOME"] = os.path.join(data_dir, ".cache", "vllm")
[docs] def install_vllm(data_dir, version="0.10.0"): # Install and setup vllm try: subprocess.run("pip install blobfile", check=True, shell=True) subprocess.run( f"pip install vllm=={version}", check=True, shell=True, ) print("Successfully installed vllm") except Exception as e: raise RuntimeError(f"Error installing vllm: {e}") setup_env_vllm(data_dir)