j4rias commited on
Commit
131aff4
Β·
verified Β·
1 Parent(s): ffe26f6

Fix: load fp16 at module level for ZeroGPU compatibility

Browse files
Files changed (1) hide show
  1. app.py +26 -36
app.py CHANGED
@@ -2,7 +2,7 @@
2
  MedVision Edge β€” Offline Chest X-ray Analysis
3
  Gradio demo for Gemma 4 E4B fine-tuned on 112K+ chest X-rays.
4
 
5
- Deploy: HuggingFace Space (GPU T4) or local with `python app.py`
6
  """
7
 
8
  import os
@@ -13,15 +13,15 @@ import gradio as gr
13
  import spaces
14
  from PIL import Image
15
  from pathlib import Path
 
16
 
17
  # ── Config ──────────────────────────────────────────────────────
18
- # HF Space: set MEDVISION_MODEL env var to HF repo ID (e.g., "user/medvision-edge-v4")
19
- # Local: defaults to local path on groundctl
20
  MODEL_PATH = os.environ.get(
21
  "MEDVISION_MODEL",
22
  os.path.expanduser("~/ml-projects/medvision/model_output/final_model"),
23
  )
24
- LOAD_IN_4BIT = os.environ.get("MEDVISION_4BIT", "true").lower() == "true"
 
25
  PATHOLOGIES = ["Pneumonia", "Consolidation", "Cardiomegaly", "Effusion", "Edema"]
26
 
27
  LANGUAGES = {
@@ -48,41 +48,30 @@ TRANSLATE_PROMPT = (
48
  "Keep medical terminology accurate. Translate only, do not add commentary.\n\n{text}"
49
  )
50
 
51
- # ── Globals (loaded once) ───────────────────────────────────────
52
- model = None
53
- processor = None
 
54
 
55
-
56
- def load_model():
57
- global model, processor
58
- if model is not None:
59
- return
60
-
61
- from transformers import AutoProcessor, AutoModelForImageTextToText, BitsAndBytesConfig
62
-
63
- print(f"Loading merged model from {MODEL_PATH} (4bit={LOAD_IN_4BIT})...")
64
-
65
- if LOAD_IN_4BIT:
66
- quantization_config = BitsAndBytesConfig(
67
  load_in_4bit=True,
68
  bnb_4bit_compute_dtype=torch.float16,
69
  bnb_4bit_quant_type="nf4",
70
- )
71
- model = AutoModelForImageTextToText.from_pretrained(
72
- MODEL_PATH,
73
- quantization_config=quantization_config,
74
- device_map="auto",
75
- )
76
- else:
77
- model = AutoModelForImageTextToText.from_pretrained(
78
- MODEL_PATH,
79
- torch_dtype=torch.float16,
80
- device_map="auto",
81
- )
82
 
83
- model.eval()
84
- processor = AutoProcessor.from_pretrained(MODEL_PATH)
85
- print("Model loaded.")
86
 
87
 
88
  def parse_response(response: str) -> dict:
@@ -137,6 +126,8 @@ def run_inference(image: Image.Image, prompt: str) -> str:
137
  return_tensors="pt",
138
  ).to(model.device)
139
 
 
 
140
  with torch.no_grad():
141
  output = model.generate(
142
  **inputs,
@@ -147,6 +138,7 @@ def run_inference(image: Image.Image, prompt: str) -> str:
147
  )
148
 
149
  decoded = processor.batch_decode(output, skip_special_tokens=True)[0]
 
150
  # Extract only the assistant's response (handle multiple Gemma 4 marker formats)
151
  for marker in ["<|turn>model", "<start_of_turn>model", "model\n"]:
152
  if marker in decoded:
@@ -165,8 +157,6 @@ def analyze_xray(image, language, patient_age, patient_weight):
165
  if image is None:
166
  return "Please upload a chest X-ray image.", "{}", "", ""
167
 
168
- load_model()
169
-
170
  # Convert to PIL if needed
171
  if not isinstance(image, Image.Image):
172
  image = Image.fromarray(image).convert("RGB")
 
2
  MedVision Edge β€” Offline Chest X-ray Analysis
3
  Gradio demo for Gemma 4 E4B fine-tuned on 112K+ chest X-rays.
4
 
5
+ Deploy: HuggingFace Space (ZeroGPU) or local with `python app.py`
6
  """
7
 
8
  import os
 
13
  import spaces
14
  from PIL import Image
15
  from pathlib import Path
16
+ from transformers import AutoProcessor, AutoModelForImageTextToText, BitsAndBytesConfig
17
 
18
  # ── Config ──────────────────────────────────────────────────────
 
 
19
  MODEL_PATH = os.environ.get(
20
  "MEDVISION_MODEL",
21
  os.path.expanduser("~/ml-projects/medvision/model_output/final_model"),
22
  )
23
+ IS_SPACES = os.environ.get("SPACE_ID") is not None
24
+ LOAD_IN_4BIT = os.environ.get("MEDVISION_4BIT", "true").lower() == "true" and not IS_SPACES
25
  PATHOLOGIES = ["Pneumonia", "Consolidation", "Cardiomegaly", "Effusion", "Edema"]
26
 
27
  LANGUAGES = {
 
48
  "Keep medical terminology accurate. Translate only, do not add commentary.\n\n{text}"
49
  )
50
 
51
+ # ── Model loading ─────────────────────────────────────────────
52
+ # ZeroGPU: load on CPU at module level, ZeroGPU moves to GPU automatically
53
+ # Local: load with device_map="auto" (optionally 4-bit)
54
+ print(f"Loading model from {MODEL_PATH} (spaces={IS_SPACES}, 4bit={LOAD_IN_4BIT})...")
55
 
56
+ if LOAD_IN_4BIT:
57
+ model = AutoModelForImageTextToText.from_pretrained(
58
+ MODEL_PATH,
59
+ quantization_config=BitsAndBytesConfig(
 
 
 
 
 
 
 
 
60
  load_in_4bit=True,
61
  bnb_4bit_compute_dtype=torch.float16,
62
  bnb_4bit_quant_type="nf4",
63
+ ),
64
+ device_map="auto",
65
+ )
66
+ else:
67
+ model = AutoModelForImageTextToText.from_pretrained(
68
+ MODEL_PATH,
69
+ torch_dtype=torch.float16,
70
+ )
 
 
 
 
71
 
72
+ model.eval()
73
+ processor = AutoProcessor.from_pretrained(MODEL_PATH)
74
+ print("Model loaded.")
75
 
76
 
77
  def parse_response(response: str) -> dict:
 
126
  return_tensors="pt",
127
  ).to(model.device)
128
 
129
+ print(f"Inference on device: {model.device}, dtype: {model.dtype}")
130
+
131
  with torch.no_grad():
132
  output = model.generate(
133
  **inputs,
 
138
  )
139
 
140
  decoded = processor.batch_decode(output, skip_special_tokens=True)[0]
141
+ print(f"Raw decoded (first 500 chars): {decoded[:500]}")
142
  # Extract only the assistant's response (handle multiple Gemma 4 marker formats)
143
  for marker in ["<|turn>model", "<start_of_turn>model", "model\n"]:
144
  if marker in decoded:
 
157
  if image is None:
158
  return "Please upload a chest X-ray image.", "{}", "", ""
159
 
 
 
160
  # Convert to PIL if needed
161
  if not isinstance(image, Image.Image):
162
  image = Image.fromarray(image).convert("RGB")