| |
| import os |
| from concurrent.futures import ThreadPoolExecutor, TimeoutError as FutureTimeoutError |
| from functools import lru_cache |
| from typing import Any, Dict |
|
|
| import torch |
| from huggingface_hub import hf_hub_download |
| from molscribe import MolScribe |
|
|
| MODEL_REPO = os.getenv("MODEL_REPO", "yujieq/MolScribe") |
| MODEL_FILE = os.getenv("MODEL_FILE", "swin_base_char_aux_1m.pth") |
| DEVICE_NAME = os.getenv("DEVICE") or ("cuda" if torch.cuda.is_available() else "cpu") |
| REQUEST_TIMEOUT_SECONDS = float(os.getenv("REQUEST_TIMEOUT_SECONDS", "180") or 0) |
|
|
| _PREDICT_EXECUTOR = ThreadPoolExecutor( |
| max_workers=1, |
| thread_name_prefix="molscribe_predict", |
| ) |
|
|
|
|
| def model_descriptor() -> Dict[str, str]: |
| return { |
| "repo": MODEL_REPO, |
| "file": MODEL_FILE, |
| "device": DEVICE_NAME, |
| } |
|
|
|
|
| @lru_cache(maxsize=1) |
| def _load_model() -> MolScribe: |
| print(f"[hf_loader] Loading MolScribe checkpoint: {MODEL_REPO}/{MODEL_FILE} on {DEVICE_NAME}") |
| ckpt_path = hf_hub_download(repo_id=MODEL_REPO, filename=MODEL_FILE) |
| model = MolScribe(ckpt_path, device=torch.device(DEVICE_NAME)) |
| print("[hf_loader] MolScribe loaded.") |
| return model |
|
|
|
|
| def warmup() -> Dict[str, str]: |
| _load_model() |
| return model_descriptor() |
|
|
|
|
| def _predict_once( |
| image_path: str, |
| return_atoms_bonds: bool, |
| return_confidence: bool, |
| ) -> Dict[str, Any]: |
| model = _load_model() |
| result = model.predict_image_file( |
| image_path, |
| return_atoms_bonds=return_atoms_bonds, |
| return_confidence=return_confidence, |
| ) |
| if not isinstance(result, dict): |
| raise TypeError(f"MolScribe 返回了非字典结果: {type(result)!r}") |
| return result |
|
|
|
|
| def _normalized_timeout_seconds(timeout_seconds: float | None) -> float | None: |
| if timeout_seconds is None: |
| timeout_seconds = REQUEST_TIMEOUT_SECONDS |
| try: |
| value = float(timeout_seconds) |
| except Exception: |
| value = 0.0 |
| if value <= 0: |
| return None |
| return value |
|
|
|
|
| def predict_image_file( |
| image_path: str, |
| return_atoms_bonds: bool = True, |
| return_confidence: bool = True, |
| timeout_seconds: float | None = None, |
| ) -> Dict[str, Any]: |
| timeout_value = _normalized_timeout_seconds(timeout_seconds) |
| future = _PREDICT_EXECUTOR.submit( |
| _predict_once, |
| image_path, |
| return_atoms_bonds, |
| return_confidence, |
| ) |
| try: |
| result = future.result(timeout=timeout_value) |
| except FutureTimeoutError as exc: |
| future.cancel() |
| if timeout_value is None: |
| raise TimeoutError("MolScribe 推理超时") from exc |
| raise TimeoutError( |
| f"MolScribe 推理超过 {timeout_value:.0f} 秒,已触发超时保护。" |
| ) from exc |
| if not isinstance(result, dict): |
| raise TypeError(f"MolScribe 返回了非字典结果: {type(result)!r}") |
| return result |
|
|