Views
No views yet
⚠️ License requirement: The base model is gated. You must have a Hugging Face account, acknowledge the Health AI Developer Foundations license, and supply aHF_TOKENwith read permissions to use this adapter.
google/medgemma-1.5-4b-itA = Healthy, B = Nodule present) and the predicted class is derived from the relative log-probabilities of those two tokens at the final input position. See the inference script below.transformers, peft, bitsandbytes, Pillow, python-dotenvHF_TOKEN in your environment (or .env file)pip install transformers peft bitsandbytes accelerate pillow python-dotenv tqdm pandas1import torch
2from PIL import Image
3from transformers import AutoModelForImageTextToText, AutoProcessor
4from peft import PeftModel
5
6BASE_MODEL = "google/medgemma-1.5-4b-it"
7ADAPTER_ID = "k298976/medgemma-1.5-4b-it-nodulocc-cls-qlora-adapter"
8
9model = AutoModelForImageTextToText.from_pretrained(
10 BASE_MODEL,
11 torch_dtype=torch.bfloat16,
12 device_map="auto",
13)
14model = PeftModel.from_pretrained(model, ADAPTER_ID)
15processor = AutoProcessor.from_pretrained(BASE_MODEL)
16model.eval()
17
18PROMPT = (
19 "You are a radiology assistant. Determine whether this frontal chest X-ray "
20 "shows a lung nodule.\n"
21 "Answer with exactly one letter:\n"
22 "A = Healthy (no lung nodule)\n"
23 "B = Nodule present"
24)
25
26messages = [
27 {
28 "role": "user",
29 "content": [
30 {"type": "image"},
31 {"type": "text", "text": PROMPT},
32 ],
33 }
34]
35
36image = Image.open("your_xray.png").convert("RGB")
37prompt_text = processor.apply_chat_template(
38 messages, add_generation_prompt=True, tokenize=False
39)
40
41inputs = processor(
42 text=[prompt_text], images=[[image]], return_tensors="pt"
43).to(model.device)
44
45a_id = processor.tokenizer.encode("A", add_special_tokens=False)[0]
46b_id = processor.tokenizer.encode("B", add_special_tokens=False)[0]
47
48with torch.inference_mode():
49 logits = model(**inputs).logits # (1, seq_len, vocab)
50
51last_logits = logits[0, -1, :] # last input position
52ab_probs = torch.softmax(last_logits[[a_id, b_id]].float(), dim=-1)
53p_nodule = ab_probs[1].item()
54
55label = "Nodule" if p_nodule >= 0.5 else "No Finding"
56print(f"Prediction: {label} | p(Nodule) = {p_nodule:.4f}")Image preprocessing: All images should be percentile-clipped to [0.5, 95.5] and rescaled to uint8 before being passed to the processor — this matches training preprocessing. The fullload_image_for_model()helper is in the inference script.
1BitsAndBytesConfig(
2 load_in_4bit=True,
3 bnb_4bit_use_double_quant=True,
4 bnb_4bit_quant_type="nf4",
5 bnb_4bit_compute_dtype=torch.bfloat16,
6 bnb_4bit_quant_storage=torch.bfloat16,
7)1LoraConfig(
2 lora_alpha=16,
3 lora_dropout=0.05,
4 r=16,
5 bias="none",
6 target_modules="all-linear",
7 task_type="CAUSAL_LM",
8)| Hyperparameter | Value |
|---|---|
| Training regime | bf16 mixed precision |
| Optimizer | AdamW (fused) |
| Peak learning rate | 2×10⁻⁴ |
| LR scheduler | Linear warmup (3% steps) + linear decay |
| Effective batch size | 16 (batch 4 × grad accum 4) |
| Total steps | 4400 |
| Model | Precision | Recall | Specificity | F1 | ROC AUC | PR AUC |
|---|---|---|---|---|---|---|
| MedGemma 1.5 Zero-shot | 0.1087 | 0.1478 | 0.9457 | 0.1253 | 0.6913 | 0.0850 |
| MedGemma 1.5 + this adapter | 0.374 | 0.3621 | 0.9728 | 0.368 | 0.7864 | 0.2384 |
1@article{sellergren2025medgemma,
2 title={MedGemma Technical Report},
3 author={Sellergren, Andrew and Kazemzadeh, Sahar and others},
4 journal={arXiv preprint arXiv:2507.05201},
5 year={2025}
6}