MedVLThinker: Simple Baselines for Multimodal Medical Reasoning

December 21, 2025 Β· View on GitHub

arXiv Project Page Hugging Face License

MedVLThinker is an open-source recipe for building reasoning-centric medical vision-language models. It bundles everything you need: cleaned text-only and/or image-text datasets, a difficulty-aware data-curation pipeline, and two turnkey training modes (SFT or RLVR), to reproduce or extend our state-of-the-art baselines on six public medical-VQA benchmarks. By scaling the same recipe from 3 B to 32 B parameters, we show that an open 32 B model can match GPT-4o on accuracy while remaining fully transparent and reproducible.

πŸ“° News

2025-12-21 β€” We’re excited to introduce MedVLSynther (https://github.com/UCSC-VLAA/MedVLSynther), a rubric-guided generator–verifier framework that synthesizes high-quality multiple-choice medical VQA items directly from open biomedical literature by grounding on figures, captions, and in-text references. Built from PubMed Central, it releases MedSynVQA (13,087 audited questions over 14,803 images across 13 imaging modalities and 28 anatomical regions), and provides a complementary data-generation pipeline you can pair with MedVLThinker’s open training recipes (SFT/RLVR) for reasoning-centric medical vision-language modeling.

πŸ”₯ Highlights

  • Fully open stack – code, filtered datasets, checkpoints, and evaluation scripts are all released under permissive licenses.
  • Simple but strong – two recipes: supervised fine-tuning (SFT) or reinforcement learning with verifiable rewards (RLVR) on curated data.
  • RLVR > SFT – RLVR consistently beats SFT across model sizes; on a 7 B backbone it lifts average accuracy from 53.5 % to 54.9 %.
  • State-of-the-art 7 B – our 7 B RLVR model tops all previous open medical LMMs on six benchmarks.
  • GPT-4o-level 32 B – scaling the same recipe to 32 B parameters reaches GPT-4o parity (63 % avg.) while staying open.
  • Data you can trust – medium-difficulty questions are auto-filtered via pass-count analysis; noisy items are dropped before training.

πŸ“‹ Table of Contents

πŸš€ Installation

Prerequisites

  • Python 3.8+
  • CUDA 11.8 or later
  • Docker (recommended)
git clone git@github.com:UCSC-VLAA/MedVLThinker.git
cd MedVLThinker

# Clone VERL for reinforcement learning
git clone https://github.com/volcengine/verl.git third_party/verl
cd third_party/verl
git checkout 54b2677
cd ../..

# Start Docker container
docker pull whatcanyousee/verl:ngc-cu124-vllm0.8.5-sglang0.4.6-mcore0.12.0-te2.3

docker run -itd \
--runtime=nvidia \
--gpus all \
--net=host \
--ipc=host \
--ulimit memlock=-1 --ulimit stack=67108864 \
--cap-add=SYS_ADMIN \
-v $(pwd):$(pwd) \
-v $HOME:$HOME \
-w $(pwd) \
-e HF_HOME=$(pwd)/cache/ \
-u $(id -u):$(id -g) \
-e HOME=$HOME \
-e USER=$USER \
--memory 900g \
--name MedVLThinker \
whatcanyousee/verl:ngc-cu124-vllm0.8.5-sglang0.4.6-mcore0.12.0-te2.3 \
bash

docker exec -it MedVLThinker bash
pip3 install -e third_party/verl[vllm]

Option 2: Local Installation

git clone git@github.com:UCSC-VLAA/MedVLThinker.git
cd MedVLThinker

# Install dependencies
pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu118
pip install transformers datasets qwen-vl-utils
pip install vllm python-dotenv wandb click tqdm matplotlib pandas

Environment Configuration

Create a .env file in the project root:

WANDB_API_KEY=your_wandb_key
WANDB_PROJECT=MedVLThinker
WANDB_MODE=online
WANDB_ENTITY=your_entity

HF_TOKEN=your_huggingface_token
HF_HOME=cache/

🎯 Quick Start

Demo

from transformers import Qwen2_5_VLForConditionalGeneration, AutoTokenizer, AutoProcessor
from qwen_vl_utils import process_vision_info
import torch

# Load the model
model_name="UCSC-VLAA/MedVLThinker-3B-RL_m23k"
model = Qwen2_5_VLForConditionalGeneration.from_pretrained(
    model_name,
    torch_dtype=torch.bfloat16,
    device_map="auto"
)
processor = AutoProcessor.from_pretrained(model_name)

# Example usage
messages_1 = [
    {
        "role": "system",
        "content": "You will solve a problem/request. You should provide your thoughts within <think> </think> tags before providing the answer.\nWrite your final answer within <answer> </answer> tags.",
    },
    {
        "role": "user",
        "content": [
            {
                "type": "image",
                "image": "assets/slake_closed.jpg",
            },
            {"type": "text", "text": "Which side of lung is abnormal in this image, left or right?"},
        ],
    }
]

messages_2 = [
    {
        "role": "system",
        "content": "You will solve a problem/request. You should provide your thoughts within <think> </think> tags before providing the answer.\nWrite your final answer within <answer> </answer> tags.",
    },
    {
        "role": "user",
        "content": [
            {
                "type": "image",
                "image": "assets/MedXpertQA-MM.jpg",
            },
            {"type": "text", "text": "You are shown images of the right and left distal common carotid arteries, respectively. What is the MOST likely diagnosis?"},
        ],
    }
]

# Preparation for inference
messages = messages_2

text = processor.apply_chat_template(
    messages, tokenize=False, add_generation_prompt=True
)
image_inputs, video_inputs = process_vision_info(messages)
inputs = processor(
    text=[text],
    images=image_inputs,
    videos=video_inputs,
    padding=True,
    return_tensors="pt",
)
inputs = inputs.to("cuda")

# Inference
generated_ids = model.generate(**inputs, max_new_tokens=2048, temperature=0.6, top_p=0.95, do_sample=True)
generated_ids_trimmed = [
    out_ids[len(in_ids) :] for in_ids, out_ids in zip(inputs.input_ids, generated_ids)
]
output_text = processor.batch_decode(
    generated_ids_trimmed, skip_special_tokens=True, clean_up_tokenization_spaces=False
)
print(output_text)

πŸ“Š Datasets

Available Datasets

Our project provides several curated datasets for medical vision-language understanding and training:

DatasetModalityDescriptionDownload
MedVLThinker-m23k-tokenizedText-onlyTokenized version of the m23k datasetπŸ€— HF
MedVLThinker-pmc_vqa-gpt_4o_reasoning-tokenizedImage-TextTokenized PMC-VQA dataset with GPT-4o generated reasoning chainsπŸ€— HF
MedVLThinker-pmc_vqaImage-TextProcessed PMC-VQA dataset for medical visual question answering with RLVRπŸ€— HF
MedVLThinker-EvalImage-TextComprehensive evaluation dataset for medical VQA benchmarksπŸ€— HF

Dataset Usage

from datasets import load_dataset

# Load evaluation dataset
eval_dataset = load_dataset("UCSC-VLAA/MedVLThinker-Eval")

# Load training dataset with reasoning
train_dataset = load_dataset("UCSC-VLAA/MedVLThinker-pmc_vqa-gpt_4o_reasoning-tokenized")

# Load PMC-VQA dataset
pmc_dataset = load_dataset("UCSC-VLAA/MedVLThinker-pmc_vqa")

# Load Medical23k tokenized dataset
m23k_dataset = load_dataset("UCSC-VLAA/MedVLThinker-m23k-tokenized")
Dataset details and preparation of your own

Supported Datasets

Our framework supports evaluation on the following medical VQA datasets:

  • PMC-VQA: PubMed Central Visual Question Answering
  • PathVQA: Pathology Visual Question Answering
  • SLAKE: Bilingual medical VQA dataset
  • VQA-RAD: Radiology Visual Question Answering
  • MMMU Medical: Medical subsets from MMMU benchmark
  • MedXpertQA: Expert-level medical questions

Data Format

All datasets follow a unified format:

{
    "images": [PIL.Image],           # List of images
    "question": str,                 # Question text
    "options": Dict[str, str],       # Multiple choice options
    "answer_label": str,             # Correct answer label (A, B, C, D)
    "answer": str,                   # Full answer text
    "reasoning": str,                # Chain-of-thought reasoning (optional)
    "dataset_name": str,             # Source dataset name
    "dataset_index": int             # Unique sample identifier
}

Prepare Evaluation Data

# Download and prepare evaluation datasets
python data_process/prepare_vlm_eval_data_v2.py

# The processed dataset will be available at: UCSC-VLAA/MedVLThinker-Eval

Prepare Training Data with Pass Rate

# Estimate pass rates for curriculum learning
bash eval/estimate_pass_rate.sh

# Order training data from easy to hard
python data_process/train_dataset/order_easy_to_hard.py \
    --dataset_name UCSC-VLAA/MedVLThinker-pmc_vqa \
    --split train \
    --results_jsonl_path outputs/estimate_pass_rate/results.jsonl \
    --save_to_disk_path data/local/pmc_vqa_easy_to_hard

πŸ‹οΈ Training

Supervised Fine-tuning (SFT)

train/sft/train_commands.sh
# text sft: train/sft/sft_local.sh
# image-text sft: train/sft/sft_vlm_local.sh

# convert SFT weights
train/sft/convert_weights.py

Training logs: https://wandb.ai/xk-huang/med-vlrm/workspace?nw=nwuserxkhuang

Reinforcement Learning (GRPO)

bash train/*.sh

# Convert VERL checkpoints for inference
python third_party/verl/scripts/model_merger.py merge \
    --backend fsdp \
    --local_dir checkpoints/medvl_grpo/step_1000/actor \
    --target_dir outputs/converted/medvl-thinker-7b

πŸ” Evaluation

Comprehensive Evaluation

# Run evaluation on all medical benchmarks
bash eval/eval_commands.sh

bash eval/eval_baselines.sh

# Analyze results
python analysis/analyze_pass_rate_v2.py
python analysis/result_viewer.py
Detailed commands

Download Pre-trained Models

# Download base models
python3 -c "import transformers; transformers.pipeline(model='Qwen/Qwen2.5-VL-3B-Instruct')"
python3 -c "import transformers; transformers.pipeline(model='Qwen/Qwen2.5-VL-7B-Instruct')"

Run Evaluation on Medical Benchmarks

# Evaluate on medical VQA benchmarks
python eval/run_offline_inference_v2.py \
    --model Qwen/Qwen2.5-VL-7B-Instruct \
    --dp_size 1 \
    --tp_size 1 \
    --temperature 0.0 \
    --max_tokens 4096 \
    --n 1 \
    --batch_size 32 \
    --output_dir outputs/evaluation

Inference with Trained Models

# Use our trained medical VLM
python eval/run_offline_inference_v2.py \
    --model UCSC-VLAA/MedVLThinker-7B-RL_m23k \
    --dp_size 1 \
    --tp_size 1 \
    --temperature 0.0 \
    --batch_size 32 \
    --output_dir outputs/medvl-thinker

πŸ“ˆ Models and Results

Available Models

ModelSizeTraining MethodTraining DataDownload
SFT Models
MedVLThinker-3B-SFT_m23k3BSFTm23kπŸ€— HF
MedVLThinker-7B-SFT_m23k7BSFTm23kπŸ€— HF
MedVLThinker-3B-SFT_PMC3BSFTPMC-VQAπŸ€— HF
MedVLThinker-7B-SFT_PMC7BSFTPMC-VQAπŸ€— HF
RL Models
MedVLThinker-3B-RL_m23k3BRLm23kπŸ€— HF
MedVLThinker-7B-RL_m23k7BRLm23kπŸ€— HF
MedVLThinker-3B-RL_PMC3BRLPMC-VQAπŸ€— HF
MedVLThinker-7B-RL_PMC7BRLPMC-VQAπŸ€— HF
MedVLThinker-32B-RL_m23k32BRLm23kπŸ€— HF
SFT + RL Models
MedVLThinker-3B-SFT_m23k-RL_PMC3BSFT + RLm23k β†’ PMC-VQAπŸ€— HF
MedVLThinker-7B-SFT_m23k-RL_PMC7BSFT + RLm23k β†’ PMC-VQAπŸ€— HF
MedVLThinker-3B-RL_m23k-RL_PMC3BRL + RLm23k β†’ PMC-VQAπŸ€— HF
MedVLThinker-7B-RL_m23k-RL_PMC7BRL + RLm23k β†’ PMC-VQAπŸ€— HF

Benchmark Results

3B and 7B with different training recipes.

ModelPMCMMMUMedX-MPathVQASLAKEVQA-RadAvg.
Qwen2.5-VL-3B-Instruct44.7744.1220.6961.9661.3062.0149.14
SFT(m23k)28.5332.5516.0042.7443.9133.0932.80
SFT(PMC)54.5547.8421.4652.7665.7958.5850.16
SFT(m23k)+RL(PMC)46.3244.3120.5243.8558.4950.9844.08
RL(m23k)47.3252.1622.9062.2863.3871.0853.19
RL(PMC)54.2248.4321.5151.6175.5662.3852.28
RL(m23k)+RL(PMC)51.3348.4322.6049.7166.1160.1749.72
Qwen2.5-VL-7B-Instruct49.3052.9418.8965.3965.7168.7553.50
SFT(m23k)34.5846.8616.4056.3554.9753.8043.83
SFT(PMC)54.6749.8021.3953.0267.7157.7250.72
SFT(m23k)+RL(PMC)43.1847.8421.8451.4360.3455.1546.63
RL(m23k)50.6756.8624.4366.8365.7964.7154.88
RL(PMC)55.3855.2924.1157.0966.5963.4853.66
RL(m23k)+RL(PMC)56.3750.9825.8048.2459.1358.0949.77

Comparison with other methods.

ModelPMCMMMUMedX-MPathVQASLAKEVQA-RadAvg.
General LMM
GPT-4o-mini51.9063.5328.5563.3375.2466.9158.24
GPT-4o58.5568.8235.9572.4376.4470.2263.74
Gemme 3 4B44.4246.6721.8959.2466.5956.8649.28
Gemme 3 27B52.0560.7830.8065.7072.6065.2057.86
Qwen2.5-VL-3B-Instruct44.7744.1220.6961.9661.3062.0149.14
Qwen2.5-VL-7B-Instruct49.3052.9418.8965.3965.7168.7553.50
Qwen2.5-VL-32B-Instruct53.2863.9227.6867.9873.2475.1260.20
Medical LMM
MedGemma 4B42.7332.558.1759.6483.4978.5550.86
MedGemma 27B36.7535.8812.1362.0977.4072.6749.49
Llava Med v1.5 Mistral 7B34.2831.3722.5656.5262.8256.7444.05
HuatuoGPT-Vision-7B53.3950.5922.0063.5375.0063.6054.69
HuatuoGPT-Vision-34B52.5457.0621.8066.7278.8574.2658.54
MedVLThinker-3B RL(m23k)47.3252.1622.9062.2863.3871.0853.19
MedVLThinker-7B RL(m23k)50.6756.8624.4366.8365.7964.7154.88
MedVLThinker-32B RL(m23k)54.3770.0034.6068.8273.9676.9663.12

πŸ“ Project Structure

MedVLThinker/
β”œβ”€β”€ analysis/           # Result analysis and visualization
β”œβ”€β”€ data_process/       # Data preprocessing and preparation
β”œβ”€β”€ docs/              # Documentation
β”œβ”€β”€ eval/              # Evaluation scripts and benchmarks
β”œβ”€β”€ train/             # Training scripts and configurations
β”œβ”€β”€ third_party/       # External dependencies (VERL)
β”œβ”€β”€ outputs/           # Experiment outputs and results
└── README.md          # This file

πŸ“„ License

This project is licensed under the Apache License 2.0 - see the LICENSE file for details.

πŸ™ Acknowledgments

  • VERL for reinforcement learning framework
  • vLLM for efficient inference
  • Qwen-VL for base vision-language models
  • Medical VQA dataset providers

πŸ“š Citation

If you find this work useful, please cite:

@article{huang2025medvlthinker,
  title={Medvlthinker: Simple baselines for multimodal medical reasoning},
  author={Huang, Xiaoke and Wu, Juncheng and Liu, Hui and Tang, Xianfeng and Zhou, Yuyin},
  journal={arXiv preprint arXiv:2508.02669},
  year={2025}
}
@article{m1_2025,
  title={m1: Unleash the potential of test-time scaling for medical reasoning with large language models},
  author={Huang, Xiaoke and Wu, Juncheng and Liu, Hui and Tang, Xianfeng and Zhou, Yuyin},
  journal={arXiv preprint arXiv:2504.00869},
  year={2025}
}