Update handler.py
Browse files- handler.py +130 -39
handler.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
| 1 |
# -*- coding: utf-8 -*-
|
| 2 |
"""
|
| 3 |
-
PULSE ECG Handler — Demo Parity + Style Hint + Robust Fallbacks + Debug
|
| 4 |
- Demo app.py ile aynı üretim ayarları:
|
| 5 |
do_sample=True, temperature=0.05, top_p=1.0, max_new_tokens=4096
|
| 6 |
- Stopping: konuşma ayırıcıda (conv.sep/sep2) güvenli token-eşleşmeli kriter
|
|
@@ -11,6 +11,7 @@ PULSE ECG Handler — Demo Parity + Style Hint + Robust Fallbacks + Debug
|
|
| 11 |
- Post-process: yalnızca whitespace/biçim temizliği
|
| 12 |
- Ekler:
|
| 13 |
* DEBUG yardımcıları (ENV: DEBUG=1)
|
|
|
|
| 14 |
* image_processor fallback (AutoProcessor → CLIPImageProcessor)
|
| 15 |
* process_images fallback (torchvision + CLIP norm)
|
| 16 |
* FastAPI wrapper: /health, /info, /query, /debug
|
|
@@ -158,6 +159,53 @@ def _normalize_whitespace(text: str) -> str:
|
|
| 158 |
def _postprocess_min(text: str) -> str:
|
| 159 |
return _normalize_whitespace(text)
|
| 160 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 161 |
# ====== Güvenli Stop Kriteri (conv separator) ======
|
| 162 |
class SafeKeywordsStoppingCriteria(StoppingCriteria):
|
| 163 |
def __init__(self, keyword: str, tokenizer):
|
|
@@ -193,7 +241,7 @@ class ChatSessionManager:
|
|
| 193 |
def __init__(self):
|
| 194 |
self.chatbot = None
|
| 195 |
self.args = None
|
| 196 |
-
|
| 197 |
def init_if_needed(self, args, model_path, tokenizer, model, image_processor, context_len):
|
| 198 |
if self.chatbot is None:
|
| 199 |
self.args = args
|
|
@@ -263,31 +311,49 @@ def generate_response(
|
|
| 263 |
device = next(chatbot.model.parameters()).device
|
| 264 |
dtype = torch.float16
|
| 265 |
|
| 266 |
-
# Görüntü ön-işleme → tensör (
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 267 |
try:
|
| 268 |
-
|
| 269 |
-
|
| 270 |
-
|
| 271 |
-
|
| 272 |
-
|
| 273 |
-
|
| 274 |
-
|
| 275 |
-
|
|
|
|
|
|
|
| 276 |
else:
|
| 277 |
-
raise
|
| 278 |
-
|
| 279 |
-
|
| 280 |
-
|
| 281 |
-
image_tensor = image_tensor.to(device=device, dtype=dtype)
|
| 282 |
-
dbg(f"[pre] tensor shape={tuple(image_tensor.shape)} dtype={image_tensor.dtype} device={image_tensor.device}")
|
| 283 |
-
except Exception as e:
|
| 284 |
-
warn(f"[pre] process_images failed: {e} → manual CLIP preprocess fallback kullanılacak.")
|
| 285 |
try:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 286 |
from torchvision import transforms
|
| 287 |
from torchvision.transforms import InterpolationMode
|
| 288 |
preprocess = transforms.Compose([
|
| 289 |
-
transforms.Resize(
|
| 290 |
-
transforms.CenterCrop(
|
| 291 |
transforms.ToTensor(),
|
| 292 |
transforms.Normalize(
|
| 293 |
mean=[0.48145466, 0.4578275, 0.40821073],
|
|
@@ -295,9 +361,10 @@ def generate_response(
|
|
| 295 |
),
|
| 296 |
])
|
| 297 |
image_tensor = preprocess(pil_img).unsqueeze(0).to(device=device, dtype=dtype)
|
| 298 |
-
dbg("[pre] manual
|
| 299 |
-
|
| 300 |
-
|
|
|
|
| 301 |
|
| 302 |
msg = (message_text or "").strip()
|
| 303 |
msg = f"{msg}\n\n{STYLE_HINT}"
|
|
@@ -454,33 +521,51 @@ def initialize_model():
|
|
| 454 |
model_.eval()
|
| 455 |
dbg(f"[init] device={next(model_.parameters()).device}, cuda_available={torch.cuda.is_available()}")
|
| 456 |
|
| 457 |
-
# ---
|
|
|
|
|
|
|
|
|
|
|
|
|
| 458 |
try:
|
| 459 |
if image_processor_ is None:
|
| 460 |
-
dbg("[init] image_processor None → AutoProcessor
|
| 461 |
try:
|
| 462 |
from transformers import AutoProcessor
|
| 463 |
image_processor_ = AutoProcessor.from_pretrained(args.model_path)
|
| 464 |
-
dbg("[init] image_processor: AutoProcessor.from_pretrained(model_path)
|
| 465 |
except Exception as _e1:
|
| 466 |
-
dbg(f"[init] AutoProcessor failed: {_e1}
|
| 467 |
-
|
| 468 |
-
|
| 469 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 470 |
except Exception as _e:
|
| 471 |
-
warn(f"[init] image_processor fallback failed: {_e}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 472 |
|
| 473 |
# --- image_processor introspection ---
|
| 474 |
try:
|
| 475 |
ip = image_processor_
|
| 476 |
if ip is not None:
|
| 477 |
crop_sz = getattr(getattr(ip, "crop_size", None), "height", None) or getattr(ip, "crop_size", None)
|
| 478 |
-
size_sz = getattr(getattr(ip, "size", None), "
|
| 479 |
dbg(f"[init] image_processor crop_size={crop_sz} size={size_sz} class={ip.__class__.__name__}")
|
| 480 |
else:
|
| 481 |
warn("[init] image_processor yine None (fallback da başarısız).")
|
| 482 |
-
except Exception as
|
| 483 |
-
warn(f"[init] image_processor inspect error: {
|
| 484 |
|
| 485 |
globals()["tokenizer"] = tokenizer_
|
| 486 |
globals()["model"] = model_
|
|
@@ -510,7 +595,7 @@ class EndpointHandler:
|
|
| 510 |
return get_model_info()
|
| 511 |
|
| 512 |
if __name__ == "__main__":
|
| 513 |
-
print("Handler ready (Demo Parity + Style Hint + whitespace post-process + fallbacks + debug). Use `EndpointHandler` or `query`.")
|
| 514 |
|
| 515 |
# ===================== Minimal FastAPI Wrapper =====================
|
| 516 |
try:
|
|
@@ -567,9 +652,14 @@ if FASTAPI_AVAILABLE:
|
|
| 567 |
ip = image_processor
|
| 568 |
ip_cls = ip.__class__.__name__ if ip else None
|
| 569 |
crop_sz = getattr(getattr(ip, "crop_size", None), "height", None) or getattr(ip, "crop_size", None)
|
| 570 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 571 |
except Exception:
|
| 572 |
-
|
| 573 |
|
| 574 |
return {
|
| 575 |
"debug": bool(DEBUG),
|
|
@@ -579,7 +669,8 @@ if FASTAPI_AVAILABLE:
|
|
| 579 |
"context_len": context_len,
|
| 580 |
"image_processor_class": ip_cls,
|
| 581 |
"image_processor_crop_size": crop_sz,
|
| 582 |
-
"image_processor_size":
|
|
|
|
| 583 |
"model_path": args.model_path if args else None,
|
| 584 |
}
|
| 585 |
|
|
|
|
| 1 |
# -*- coding: utf-8 -*-
|
| 2 |
"""
|
| 3 |
+
PULSE ECG Handler — Demo Parity + Style Hint + Robust Fallbacks + Debug + Dynamic Vision Size
|
| 4 |
- Demo app.py ile aynı üretim ayarları:
|
| 5 |
do_sample=True, temperature=0.05, top_p=1.0, max_new_tokens=4096
|
| 6 |
- Stopping: konuşma ayırıcıda (conv.sep/sep2) güvenli token-eşleşmeli kriter
|
|
|
|
| 11 |
- Post-process: yalnızca whitespace/biçim temizliği
|
| 12 |
- Ekler:
|
| 13 |
* DEBUG yardımcıları (ENV: DEBUG=1)
|
| 14 |
+
* Dynamic vision size: vision tower -> processor + preprocess/fallback
|
| 15 |
* image_processor fallback (AutoProcessor → CLIPImageProcessor)
|
| 16 |
* process_images fallback (torchvision + CLIP norm)
|
| 17 |
* FastAPI wrapper: /health, /info, /query, /debug
|
|
|
|
| 159 |
def _postprocess_min(text: str) -> str:
|
| 160 |
return _normalize_whitespace(text)
|
| 161 |
|
| 162 |
+
# ====== Vision helpers (dynamic size) ======
|
| 163 |
+
def get_vision_expected_size(m, default: int = 336) -> int:
|
| 164 |
+
"""
|
| 165 |
+
Modelin vision tower'ının beklediği input boyutunu döndürür (örn. 336).
|
| 166 |
+
LLaVA/CLIP konfiglerinde genelde `image_size` bulunur.
|
| 167 |
+
"""
|
| 168 |
+
try:
|
| 169 |
+
vt = m.get_vision_tower()
|
| 170 |
+
vt_cfg = getattr(getattr(vt, "vision_tower", vt), "config", None)
|
| 171 |
+
if vt_cfg is None:
|
| 172 |
+
return default
|
| 173 |
+
if getattr(vt_cfg, "image_size", None):
|
| 174 |
+
return int(vt_cfg.image_size)
|
| 175 |
+
vc = getattr(vt_cfg, "vision_config", None)
|
| 176 |
+
if vc and getattr(vc, "image_size", None):
|
| 177 |
+
return int(vc.image_size)
|
| 178 |
+
except Exception as e:
|
| 179 |
+
dbg(f"[get_vision_expected_size] fallback default={default} because: {e}")
|
| 180 |
+
return default
|
| 181 |
+
|
| 182 |
+
def force_processor_size(proc, size: int):
|
| 183 |
+
"""Processor'ın resize/crop alanlarını güvenle hedef boyuta zorlar."""
|
| 184 |
+
try:
|
| 185 |
+
# size
|
| 186 |
+
if hasattr(proc, "size"):
|
| 187 |
+
if isinstance(proc.size, dict):
|
| 188 |
+
proc.size["shortest_edge"] = size
|
| 189 |
+
else:
|
| 190 |
+
try:
|
| 191 |
+
proc.size.shortest_edge = size # type: ignore[attr-defined]
|
| 192 |
+
except Exception:
|
| 193 |
+
proc.size = {"shortest_edge": size}
|
| 194 |
+
# crop_size
|
| 195 |
+
if hasattr(proc, "crop_size"):
|
| 196 |
+
if isinstance(proc.crop_size, dict):
|
| 197 |
+
proc.crop_size["height"] = size
|
| 198 |
+
proc.crop_size["width"] = size
|
| 199 |
+
else:
|
| 200 |
+
try:
|
| 201 |
+
proc.crop_size.height = size # type: ignore[attr-defined]
|
| 202 |
+
proc.crop_size.width = size # type: ignore[attr-defined]
|
| 203 |
+
except Exception:
|
| 204 |
+
proc.crop_size = {"height": size, "width": size}
|
| 205 |
+
dbg(f"[processor] forced size={size}")
|
| 206 |
+
except Exception as e:
|
| 207 |
+
warn(f"[processor] force size failed: {e}")
|
| 208 |
+
|
| 209 |
# ====== Güvenli Stop Kriteri (conv separator) ======
|
| 210 |
class SafeKeywordsStoppingCriteria(StoppingCriteria):
|
| 211 |
def __init__(self, keyword: str, tokenizer):
|
|
|
|
| 241 |
def __init__(self):
|
| 242 |
self.chatbot = None
|
| 243 |
self.args = None
|
| 244 |
+
self.model_path = None
|
| 245 |
def init_if_needed(self, args, model_path, tokenizer, model, image_processor, context_len):
|
| 246 |
if self.chatbot is None:
|
| 247 |
self.args = args
|
|
|
|
| 311 |
device = next(chatbot.model.parameters()).device
|
| 312 |
dtype = torch.float16
|
| 313 |
|
| 314 |
+
# === Görüntü ön-işleme → tensör (dinamik boy) ===
|
| 315 |
+
expected_size = get_vision_expected_size(chatbot.model, default=336)
|
| 316 |
+
dbg(f"[pre] dynamic expected_size={expected_size} | processor={type(chatbot.image_processor)}")
|
| 317 |
+
|
| 318 |
+
# 3.1) Processor.preprocess varsa kullan (en stabil yol)
|
| 319 |
+
image_tensor = None
|
| 320 |
try:
|
| 321 |
+
if hasattr(chatbot.image_processor, "preprocess"):
|
| 322 |
+
px = chatbot.image_processor.preprocess(pil_img, return_tensors="pt")
|
| 323 |
+
image_tensor = px.get("pixel_values", px)
|
| 324 |
+
if not isinstance(image_tensor, torch.Tensor):
|
| 325 |
+
# Bazı processor'lar nested dict döndürebilir
|
| 326 |
+
image_tensor = image_tensor["pixel_values"]
|
| 327 |
+
if image_tensor.ndim == 3:
|
| 328 |
+
image_tensor = image_tensor.unsqueeze(0)
|
| 329 |
+
image_tensor = image_tensor.to(device=device, dtype=dtype)
|
| 330 |
+
dbg(f"[pre] processor.preprocess ok → {tuple(image_tensor.shape)}")
|
| 331 |
else:
|
| 332 |
+
raise AttributeError("processor has no preprocess")
|
| 333 |
+
except Exception as e_pre:
|
| 334 |
+
warn(f"[pre] processor.preprocess not used: {e_pre} → process_images denenecek…")
|
| 335 |
+
# 3.2) LLaVA'nın process_images yolu
|
|
|
|
|
|
|
|
|
|
|
|
|
| 336 |
try:
|
| 337 |
+
processed = process_images([pil_img], chatbot.image_processor, chatbot.model.config)
|
| 338 |
+
if isinstance(processed, (list, tuple)) and len(processed) > 0:
|
| 339 |
+
image_tensor = processed[0]
|
| 340 |
+
elif isinstance(processed, torch.Tensor):
|
| 341 |
+
image_tensor = processed[0] if processed.ndim == 4 else processed
|
| 342 |
+
else:
|
| 343 |
+
raise ValueError("process_images returned empty")
|
| 344 |
+
|
| 345 |
+
if image_tensor.ndim == 3:
|
| 346 |
+
image_tensor = image_tensor.unsqueeze(0)
|
| 347 |
+
image_tensor = image_tensor.to(device=device, dtype=dtype)
|
| 348 |
+
dbg(f"[pre] process_images ok → {tuple(image_tensor.shape)}")
|
| 349 |
+
except Exception as e_proc:
|
| 350 |
+
warn(f"[pre] process_images failed: {e_proc} → manual CLIP fallback (dinamik size) kullanılacak.")
|
| 351 |
+
# 3.3) Manuel CLIP fallback (dinamik expected_size)
|
| 352 |
from torchvision import transforms
|
| 353 |
from torchvision.transforms import InterpolationMode
|
| 354 |
preprocess = transforms.Compose([
|
| 355 |
+
transforms.Resize(expected_size, interpolation=InterpolationMode.BICUBIC),
|
| 356 |
+
transforms.CenterCrop(expected_size),
|
| 357 |
transforms.ToTensor(),
|
| 358 |
transforms.Normalize(
|
| 359 |
mean=[0.48145466, 0.4578275, 0.40821073],
|
|
|
|
| 361 |
),
|
| 362 |
])
|
| 363 |
image_tensor = preprocess(pil_img).unsqueeze(0).to(device=device, dtype=dtype)
|
| 364 |
+
dbg(f"[pre] manual fallback ok → {tuple(image_tensor.shape)}")
|
| 365 |
+
|
| 366 |
+
if image_tensor is None:
|
| 367 |
+
return {"error": "Image processing failed (no tensor produced)"}
|
| 368 |
|
| 369 |
msg = (message_text or "").strip()
|
| 370 |
msg = f"{msg}\n\n{STYLE_HINT}"
|
|
|
|
| 521 |
model_.eval()
|
| 522 |
dbg(f"[init] device={next(model_.parameters()).device}, cuda_available={torch.cuda.is_available()}")
|
| 523 |
|
| 524 |
+
# --- vision tower beklenen image_size'ı al ---
|
| 525 |
+
expected_size = get_vision_expected_size(model_, default=336)
|
| 526 |
+
dbg(f"[init] vision expected image_size={expected_size}")
|
| 527 |
+
|
| 528 |
+
# --- image_processor fallback zinciri (model path > AutoProcessor > CLIP 224/336) ---
|
| 529 |
try:
|
| 530 |
if image_processor_ is None:
|
| 531 |
+
dbg("[init] image_processor None → AutoProcessor(model_path) deneniyor…")
|
| 532 |
try:
|
| 533 |
from transformers import AutoProcessor
|
| 534 |
image_processor_ = AutoProcessor.from_pretrained(args.model_path)
|
| 535 |
+
dbg("[init] image_processor: AutoProcessor.from_pretrained(model_path) yüklendi.")
|
| 536 |
except Exception as _e1:
|
| 537 |
+
dbg(f"[init] AutoProcessor(model_path) failed: {_e1}")
|
| 538 |
+
try:
|
| 539 |
+
from transformers import AutoProcessor
|
| 540 |
+
clip_id = "openai/clip-vit-large-patch14-336" if expected_size >= 336 else "openai/clip-vit-large-patch14"
|
| 541 |
+
image_processor_ = AutoProcessor.from_pretrained(clip_id)
|
| 542 |
+
dbg(f"[init] AutoProcessor({clip_id}) yüklendi.")
|
| 543 |
+
except Exception as _e2:
|
| 544 |
+
from transformers import CLIPImageProcessor
|
| 545 |
+
clip_id = "openai/clip-vit-large-patch14-336" if expected_size >= 336 else "openai/clip-vit-large-patch14"
|
| 546 |
+
image_processor_ = CLIPImageProcessor.from_pretrained(clip_id)
|
| 547 |
+
warn(f"[init] CLIPImageProcessor({clip_id}) fallback kullanılıyor.")
|
| 548 |
except Exception as _e:
|
| 549 |
+
warn(f"[init] image_processor fallback chain failed: {_e}")
|
| 550 |
+
|
| 551 |
+
# --- processor'ın boyutlarını vision tower'a uydur ---
|
| 552 |
+
try:
|
| 553 |
+
if image_processor_ is not None:
|
| 554 |
+
force_processor_size(image_processor_, expected_size)
|
| 555 |
+
except Exception as e_ip:
|
| 556 |
+
warn(f"[init] processor size set error: {e_ip}")
|
| 557 |
|
| 558 |
# --- image_processor introspection ---
|
| 559 |
try:
|
| 560 |
ip = image_processor_
|
| 561 |
if ip is not None:
|
| 562 |
crop_sz = getattr(getattr(ip, "crop_size", None), "height", None) or getattr(ip, "crop_size", None)
|
| 563 |
+
size_sz = getattr(getattr(ip, "size", None), "shortest_edge", None) or getattr(ip, "size", None)
|
| 564 |
dbg(f"[init] image_processor crop_size={crop_sz} size={size_sz} class={ip.__class__.__name__}")
|
| 565 |
else:
|
| 566 |
warn("[init] image_processor yine None (fallback da başarısız).")
|
| 567 |
+
except Exception as e_ip2:
|
| 568 |
+
warn(f"[init] image_processor inspect error: {e_ip2}")
|
| 569 |
|
| 570 |
globals()["tokenizer"] = tokenizer_
|
| 571 |
globals()["model"] = model_
|
|
|
|
| 595 |
return get_model_info()
|
| 596 |
|
| 597 |
if __name__ == "__main__":
|
| 598 |
+
print("Handler ready (Demo Parity + Style Hint + whitespace post-process + dynamic size + fallbacks + debug). Use `EndpointHandler` or `query`.")
|
| 599 |
|
| 600 |
# ===================== Minimal FastAPI Wrapper =====================
|
| 601 |
try:
|
|
|
|
| 652 |
ip = image_processor
|
| 653 |
ip_cls = ip.__class__.__name__ if ip else None
|
| 654 |
crop_sz = getattr(getattr(ip, "crop_size", None), "height", None) or getattr(ip, "crop_size", None)
|
| 655 |
+
size_short = getattr(getattr(ip, "size", None), "shortest_edge", None) or getattr(ip, "size", None)
|
| 656 |
+
except Exception:
|
| 657 |
+
ip_cls, crop_sz, size_short = None, None, None
|
| 658 |
+
|
| 659 |
+
try:
|
| 660 |
+
ve = get_vision_expected_size(model, default=None) if model else None
|
| 661 |
except Exception:
|
| 662 |
+
ve = None
|
| 663 |
|
| 664 |
return {
|
| 665 |
"debug": bool(DEBUG),
|
|
|
|
| 669 |
"context_len": context_len,
|
| 670 |
"image_processor_class": ip_cls,
|
| 671 |
"image_processor_crop_size": crop_sz,
|
| 672 |
+
"image_processor_size": {"shortest_edge": size_short},
|
| 673 |
+
"vision_expected_image_size": ve,
|
| 674 |
"model_path": args.model_path if args else None,
|
| 675 |
}
|
| 676 |
|