File size: 5,469 Bytes
e994c16 2fb233c e994c16 63c4f20 e994c16 2fb233c 63c4f20 5f76dd3 e994c16 5a17b94 e994c16 2fb233c 63c4f20 5f76dd3 5f0f16b e994c16 0245824 e994c16 63c4f20 e994c16 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 | """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)
@dataclass(frozen=True)
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
@dataclass(frozen=True)
class ReasoningConfig:
model_id: str
max_new_tokens: int
@dataclass(frozen=True)
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",
]
|