Spaces:
Sleeping
Sleeping
| """Runtime configuration for Project Halide. | |
| This module intentionally contains no model imports. It is safe to import in | |
| local CPU-only tooling, tests, and dataset preparation scripts. | |
| """ | |
| from __future__ import annotations | |
| import os | |
| from dataclasses import dataclass | |
| from pathlib import Path | |
| REPO_ROOT = Path(__file__).resolve().parent | |
| DATA_DIR = REPO_ROOT / "data" | |
| STORAGE_DIR = REPO_ROOT / "storage" | |
| CHECKPOINT_DIR = REPO_ROOT / "checkpoints" | |
| CANONICAL_VISION_MODEL_ID = "openbmb/MiniCPM-V-4.6" | |
| VISION_MODEL_ALIASES = { | |
| "openbmb/MiniCPM-V-4_6": CANONICAL_VISION_MODEL_ID, | |
| } | |
| DEFAULT_FINETUNED_MODEL_ID = "Lonelyguyse1/halide-vision" | |
| DEFAULT_REASONING_MODEL_ID = "nvidia/Nemotron-Mini-4B-Instruct" | |
| def env_bool(name: str, default: bool = False) -> bool: | |
| value = os.getenv(name) | |
| if value is None: | |
| return default | |
| return value.strip().lower() in {"1", "true", "yes", "on"} | |
| def env_int(name: str, default: int) -> int: | |
| value = os.getenv(name) | |
| if value is None or value.strip() == "": | |
| return default | |
| return int(value) | |
| def env_float(name: str, default: float) -> float: | |
| value = os.getenv(name) | |
| if value is None or value.strip() == "": | |
| return default | |
| return float(value) | |
| def env_path(name: str, default: Path) -> Path: | |
| value = os.getenv(name) | |
| return Path(value) if value else default | |
| def normalize_model_id(model_id: str) -> str: | |
| return VISION_MODEL_ALIASES.get(model_id, model_id) | |
| class VisionConfig: | |
| base_model_id: str | |
| finetuned_model_id: str | |
| local_model_path: Path | |
| use_finetuned: bool | |
| downsample_mode: str | |
| max_slice_nums: int | |
| max_new_tokens: int | |
| max_input_pixels: int | |
| tile_fallback_enabled: bool | |
| tile_fallback_min_defects: int | |
| tile_min_side: int | |
| tile_max_side: int | |
| tile_overlap: float | |
| tile_max_tiles: int | |
| classical_assist_enabled: bool | |
| classical_assist_max_defects: int | |
| class ReasoningConfig: | |
| model_id: str | |
| max_new_tokens: int | |
| class AppConfig: | |
| db_path: Path | |
| cache_size: int | |
| cache_ttl_seconds: int | |
| gpu_duration_seconds: int | |
| max_history_items: int | |
| def get_vision_config() -> VisionConfig: | |
| return VisionConfig( | |
| base_model_id=normalize_model_id( | |
| os.getenv("HALIDE_VISION_BASE_MODEL_ID", CANONICAL_VISION_MODEL_ID) | |
| ), | |
| finetuned_model_id=os.getenv( | |
| "HALIDE_VISION_FINETUNED_MODEL_ID", DEFAULT_FINETUNED_MODEL_ID | |
| ), | |
| local_model_path=env_path( | |
| "HALIDE_VISION_LOCAL_MODEL_PATH", | |
| CHECKPOINT_DIR / "minicpm-v-4.6-merged-v4-stage1", | |
| ), | |
| use_finetuned=env_bool("HALIDE_USE_FINETUNED_VISION", False), | |
| downsample_mode=os.getenv("HALIDE_DOWNSAMPLE_MODE", "4x"), | |
| max_slice_nums=env_int("HALIDE_MAX_SLICE_NUMS", 36), | |
| max_new_tokens=env_int("HALIDE_MAX_NEW_TOKENS", 2048), | |
| max_input_pixels=env_int("HALIDE_MAX_INPUT_PIXELS", 4_000_000), | |
| tile_fallback_enabled=env_bool("HALIDE_ENABLE_TILE_FALLBACK", True), | |
| tile_fallback_min_defects=env_int("HALIDE_TILE_FALLBACK_MIN_DEFECTS", 1), | |
| tile_min_side=env_int("HALIDE_TILE_MIN_SIDE", 900), | |
| tile_max_side=env_int("HALIDE_TILE_MAX_SIDE", 960), | |
| tile_overlap=env_float("HALIDE_TILE_OVERLAP", 0.35), | |
| tile_max_tiles=env_int("HALIDE_TILE_MAX_TILES", 9), | |
| classical_assist_enabled=env_bool("HALIDE_ENABLE_CLASSICAL_ASSIST", True), | |
| classical_assist_max_defects=env_int("HALIDE_CLASSICAL_ASSIST_MAX_DEFECTS", 8), | |
| ) | |
| def get_reasoning_config() -> ReasoningConfig: | |
| return ReasoningConfig( | |
| model_id=os.getenv("HALIDE_REASONING_MODEL_ID", DEFAULT_REASONING_MODEL_ID), | |
| max_new_tokens=env_int("HALIDE_NEMOTRON_MAX_TOKENS", 768), | |
| ) | |
| def get_app_config() -> AppConfig: | |
| return AppConfig( | |
| db_path=env_path("HALIDE_DB_PATH", STORAGE_DIR / "halide.db"), | |
| cache_size=env_int("HALIDE_CACHE_SIZE", 64), | |
| cache_ttl_seconds=env_int("HALIDE_CACHE_TTL_SECONDS", 3600), | |
| gpu_duration_seconds=env_int("HALIDE_GPU_DURATION_SECONDS", 120), | |
| max_history_items=env_int("HALIDE_HISTORY_LIMIT", 10), | |
| ) | |
| def running_on_hugging_face_space() -> bool: | |
| return bool(os.getenv("SPACE_ID") or os.getenv("SPACE_HOST")) | |
| def require_gpu_for_inference(stage: str) -> None: | |
| """Refuse model inference unless a CUDA device is visible. | |
| Local CPU use is allowed for file I/O, JSON parsing, image resizing, tests, | |
| and dataset preparation. It is not allowed for loading or running the | |
| vision or reasoning models. | |
| """ | |
| import torch | |
| if torch.cuda.is_available(): | |
| return | |
| raise RuntimeError( | |
| f"Halide refused to run {stage} model inference because no CUDA GPU " | |
| "is visible. Run inference on Modal, Hugging Face ZeroGPU, or another " | |
| "GPU runtime. Local CPU is reserved for editing, parsing, and tests." | |
| ) | |
| __all__ = [ | |
| "AppConfig", | |
| "CHECKPOINT_DIR", | |
| "CANONICAL_VISION_MODEL_ID", | |
| "DATA_DIR", | |
| "DEFAULT_FINETUNED_MODEL_ID", | |
| "DEFAULT_REASONING_MODEL_ID", | |
| "REPO_ROOT", | |
| "ReasoningConfig", | |
| "STORAGE_DIR", | |
| "VisionConfig", | |
| "env_bool", | |
| "env_float", | |
| "env_int", | |
| "env_path", | |
| "get_app_config", | |
| "get_reasoning_config", | |
| "get_vision_config", | |
| "normalize_model_id", | |
| "require_gpu_for_inference", | |
| "running_on_hugging_face_space", | |
| ] | |