Spaces:
Runtime error
Runtime error
| import os | |
| import torch | |
| from fastapi import FastAPI, Header, HTTPException | |
| from pydantic import BaseModel | |
| from peft import PeftConfig, PeftModel | |
| from transformers import AutoModelForCausalLM, AutoTokenizer, BitsAndBytesConfig | |
| ADAPTER_MODEL_ID = os.environ.get( | |
| "ADAPTER_MODEL_ID", | |
| "maimd/Maimd-HPI-SFT-Behavioral-MedGemma-4B-v001-20260608", | |
| ).strip() | |
| MODEL_ID = os.environ.get("MODEL_ID", "").strip() | |
| HF_TOKEN = os.environ.get("HF_TOKEN") | |
| REMOTE_API_KEY = os.environ.get("REMOTE_API_KEY", "") | |
| app = FastAPI(title="Agentic Dr Inference") | |
| model = None | |
| tokenizer = None | |
| resolved_model_id = None | |
| resolved_base_model_id = None | |
| class GenerateRequest(BaseModel): | |
| prompt: str | |
| max_new_tokens: int = 300 | |
| def load_model(): | |
| global model, tokenizer, resolved_model_id, resolved_base_model_id | |
| if model is not None and tokenizer is not None: | |
| return | |
| device = "cuda" if torch.cuda.is_available() else "cpu" | |
| quantization_config = None | |
| if device == "cuda": | |
| quantization_config = BitsAndBytesConfig( | |
| load_in_4bit=True, | |
| bnb_4bit_compute_dtype=torch.bfloat16, | |
| bnb_4bit_use_double_quant=True, | |
| bnb_4bit_quant_type="nf4", | |
| ) | |
| target_model_id = ADAPTER_MODEL_ID or MODEL_ID | |
| if not target_model_id: | |
| raise RuntimeError("Either ADAPTER_MODEL_ID or MODEL_ID must be configured.") | |
| resolved_model_id = target_model_id | |
| adapter_model_id = None | |
| try: | |
| peft_config = PeftConfig.from_pretrained( | |
| target_model_id, | |
| token=HF_TOKEN, | |
| ) | |
| adapter_model_id = target_model_id | |
| resolved_base_model_id = peft_config.base_model_name_or_path | |
| except Exception: | |
| resolved_base_model_id = None | |
| tokenizer_source = resolved_base_model_id or target_model_id | |
| tokenizer = AutoTokenizer.from_pretrained( | |
| tokenizer_source, | |
| token=HF_TOKEN, | |
| ) | |
| if tokenizer.pad_token is None: | |
| tokenizer.pad_token = tokenizer.eos_token | |
| base_model = AutoModelForCausalLM.from_pretrained( | |
| resolved_base_model_id or target_model_id, | |
| token=HF_TOKEN, | |
| torch_dtype=torch.bfloat16 if device == "cuda" else torch.float32, | |
| device_map="auto" if device == "cuda" else None, | |
| low_cpu_mem_usage=True, | |
| quantization_config=quantization_config, | |
| ) | |
| if adapter_model_id: | |
| model = PeftModel.from_pretrained( | |
| base_model, | |
| adapter_model_id, | |
| token=HF_TOKEN, | |
| ).merge_and_unload() | |
| else: | |
| model = base_model | |
| model.eval() | |
| def health(): | |
| return { | |
| "status": "ok", | |
| "model_id": resolved_model_id or MODEL_ID or ADAPTER_MODEL_ID, | |
| "base_model_id": resolved_base_model_id, | |
| } | |
| def generate(request: GenerateRequest, authorization: str | None = Header(default=None)): | |
| if REMOTE_API_KEY: | |
| expected = f"Bearer {REMOTE_API_KEY}" | |
| if authorization != expected: | |
| raise HTTPException(status_code=401, detail="unauthorized") | |
| if not request.prompt.strip(): | |
| raise HTTPException(status_code=400, detail="prompt is required") | |
| load_model() | |
| messages = [ | |
| { | |
| "role": "user", | |
| "content": request.prompt, | |
| } | |
| ] | |
| prompt_text = tokenizer.apply_chat_template( | |
| messages, | |
| add_generation_prompt=True, | |
| tokenize=False, | |
| ) | |
| inputs = tokenizer(prompt_text, return_tensors="pt") | |
| model_input_device = next(model.parameters()).device | |
| inputs = { | |
| key: value.to(model_input_device) if isinstance(value, torch.Tensor) else value | |
| for key, value in inputs.items() | |
| } | |
| input_len = inputs["input_ids"].shape[-1] | |
| with torch.inference_mode(): | |
| outputs = model.generate( | |
| **inputs, | |
| max_new_tokens=request.max_new_tokens, | |
| do_sample=False, | |
| pad_token_id=tokenizer.eos_token_id, | |
| ) | |
| generated_tokens = outputs[0][input_len:] | |
| text = tokenizer.decode(generated_tokens, skip_special_tokens=True).strip() | |
| return {"text": text} | |