Spaces:
Runtime error
Runtime error
File size: 8,299 Bytes
2306d9c | 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 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 | """
model_loader.py
---------------
Loads all AI models:
- LLM: epfl-llm/meditron-7b (4-bit NF4, ~5GB VRAM)
- Vision: Salesforce/blip2-opt-2.7b (float16, ~3.5GB VRAM)
VRAM budget on RTX 4060 8GB:
Meditron-7B 4-bit : ~5.0 GB
BLIP-2 : ~3.5 GB
NOTE: Run them sequentially (caption first, then LLM)
so both never sit in VRAM simultaneously.
Or use 8GB by loading BLIP-2, captioning, unloading,
then loading Meditron. This is handled automatically.
"""
import gc
import logging
import os
from pathlib import Path
import torch
logger = logging.getLogger(__name__)
SLM_MODEL_ID = os.getenv("SLM_MODEL_ID", "BioMistral/BioMistral-7B")
VISION_MODEL_ID = os.getenv("VISION_MODEL_ID", "Salesforce/blip2-opt-2.7b")
LORA_ADAPTER_PATH = os.getenv("LORA_ADAPTER_PATH", "models/slm_model/lora_adapter")
USE_4BIT = os.getenv("USE_4BIT", "true").lower() == "true"
DEVICE = "cuda" if torch.cuda.is_available() else "cpu"
logger.info(f"Device: {DEVICE} | 4-bit: {USE_4BIT}")
if DEVICE == "cuda":
logger.info(
f"GPU: {torch.cuda.get_device_name(0)} | "
f"VRAM: {torch.cuda.get_device_properties(0).total_memory/1e9:.1f}GB"
)
_vision_model = None
_vision_processor = None
_slm_model = None
_slm_tokenizer = None
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
# VISION β BLIP-2 (generates real image captions)
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
def load_vision_encoder():
global _vision_model, _vision_processor
if _vision_model is not None:
return _vision_model, _vision_processor
logger.info(f"Loading BLIP-2: {VISION_MODEL_ID}")
from transformers import Blip2ForConditionalGeneration, Blip2Processor
_vision_processor = Blip2Processor.from_pretrained(
VISION_MODEL_ID, cache_dir="models/vision_encoder"
)
if DEVICE == "cuda":
_vision_model = Blip2ForConditionalGeneration.from_pretrained(
VISION_MODEL_ID,
torch_dtype=torch.float16,
device_map="auto",
cache_dir="models/vision_encoder",
)
else:
_vision_model = Blip2ForConditionalGeneration.from_pretrained(
VISION_MODEL_ID,
torch_dtype=torch.float32,
cache_dir="models/vision_encoder",
)
_vision_model.eval()
logger.info("BLIP-2 loaded.")
return _vision_model, _vision_processor
def unload_vision_encoder():
"""Free VRAM after captioning so Meditron-7B can load."""
global _vision_model, _vision_processor
if _vision_model is not None:
del _vision_model
_vision_model = None
_vision_processor = None
if DEVICE == "cuda":
torch.cuda.empty_cache()
gc.collect()
logger.info("BLIP-2 unloaded β VRAM freed for Meditron.")
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
# LLM β Meditron-7B (medical language model)
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
def load_slm():
global _slm_model, _slm_tokenizer
if _slm_model is not None:
return _slm_model, _slm_tokenizer
logger.info(f"Loading LLM: {SLM_MODEL_ID} | 4-bit: {USE_4BIT}")
from transformers import AutoModelForCausalLM, AutoTokenizer
_slm_tokenizer = AutoTokenizer.from_pretrained(
SLM_MODEL_ID,
trust_remote_code=True,
padding_side="left",
cache_dir="models/slm_model",
)
if _slm_tokenizer.pad_token is None:
_slm_tokenizer.pad_token = _slm_tokenizer.eos_token
if USE_4BIT and DEVICE == "cuda":
from transformers import BitsAndBytesConfig
bnb = BitsAndBytesConfig(
load_in_4bit=True,
bnb_4bit_use_double_quant=True,
bnb_4bit_quant_type="nf4",
bnb_4bit_compute_dtype=torch.bfloat16,
)
_slm_model = AutoModelForCausalLM.from_pretrained(
SLM_MODEL_ID,
quantization_config=bnb,
device_map="auto",
trust_remote_code=True,
cache_dir="models/slm_model",
)
logger.info("Meditron-7B loaded 4-bit NF4 (~5GB VRAM)")
elif DEVICE == "cuda":
_slm_model = AutoModelForCausalLM.from_pretrained(
SLM_MODEL_ID,
torch_dtype=torch.float16,
device_map="auto",
trust_remote_code=True,
cache_dir="models/slm_model",
)
logger.info("Meditron-7B loaded float16 (~14GB VRAM)")
else:
logger.warning("No GPU β CPU inference will be very slow")
_slm_model = AutoModelForCausalLM.from_pretrained(
SLM_MODEL_ID,
torch_dtype=torch.float32,
trust_remote_code=True,
low_cpu_mem_usage=True,
cache_dir="models/slm_model",
)
_slm_model = _apply_lora_adapter(_slm_model)
_slm_model.eval()
logger.info("Meditron-7B ready.")
return _slm_model, _slm_tokenizer
def _apply_lora_adapter(model):
adapter_path = Path(LORA_ADAPTER_PATH)
if not (adapter_path / "adapter_config.json").exists():
logger.info("No LoRA adapter β using base Meditron-7B.")
return model
try:
from peft import PeftModel
model = PeftModel.from_pretrained(model, str(adapter_path))
model = model.merge_and_unload()
logger.info(f"LoRA adapter loaded from {adapter_path}")
except Exception as e:
logger.warning(f"LoRA load failed: {e} β using base model.")
return model
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
# Image captioning β BLIP-2
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
def generate_image_caption(pil_image, vision_model=None,
vision_processor=None) -> str:
if vision_model is None or vision_processor is None:
vision_model, vision_processor = load_vision_encoder()
prompt = (
"Question: Describe this medical radiology image in detail. "
"Include all visible anatomy, any hardware or implants, "
"any pathology or abnormalities, and the image quality. Answer:"
)
try:
inputs = vision_processor(
images=pil_image, text=prompt, return_tensors="pt"
)
if DEVICE == "cuda":
inputs = {
k: v.to(DEVICE).to(torch.float16)
if v.dtype == torch.float32 else v.to(DEVICE)
for k, v in inputs.items()
}
with torch.no_grad():
ids = vision_model.generate(
**inputs,
max_new_tokens=150,
num_beams=4,
repetition_penalty=1.3,
)
caption = vision_processor.decode(ids[0], skip_special_tokens=True)
if "Answer:" in caption:
caption = caption.split("Answer:")[-1].strip()
logger.info(f"BLIP-2 caption: {caption[:150]}")
return caption
except Exception as e:
logger.warning(f"BLIP-2 failed: {e}")
return ""
def extract_visual_features(pil_image, vision_model=None,
vision_processor=None):
caption = generate_image_caption(pil_image, vision_model, vision_processor)
return None, caption
|