Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
122 commits
Select commit Hold shift + click to select a range
6ca8bde
[GRPO] vlm training
Mar 13, 2025
8f34987
test vlm training
Mar 13, 2025
328ef46
Merge remote-tracking branch 'upstream/main' into grpo_vlm
Mar 20, 2025
e94b5fa
Merge remote-tracking branch 'upstream/main' into grpo_vlm
Mar 25, 2025
eb453d9
Merge branch 'main' into grpo_vlm
kashif Jul 9, 2025
9f6517d
fix merge
kashif Jul 9, 2025
ac9ba6f
fix imports
kashif Jul 9, 2025
3b38035
fix merge issues and some tests
kashif Jul 9, 2025
3694f35
fix config merge
kashif Jul 9, 2025
c79c97d
fix tests
kashif Jul 9, 2025
ef89aec
undo change from merge
kashif Jul 9, 2025
1e7111a
fix merge typo
kashif Jul 9, 2025
4f2c19d
test smolvm
kashif Jul 9, 2025
1923a5a
do not set to false with vlm
kashif Jul 10, 2025
01fa042
enable vllm
kashif Jul 10, 2025
f9521ae
add docs
kashif Jul 10, 2025
e552d13
validate images helper
kashif Jul 10, 2025
7eddba9
Update docs/source/grpo_trainer.md
kashif Jul 10, 2025
c965d0e
set the _set_signature_columns_if_needed
kashif Jul 10, 2025
ab80abc
make tests a bit more light weight
kashif Jul 10, 2025
88e0b77
Added grpo_vlm.py example script
sergiopaniego Jul 10, 2025
cecdaec
Updated script example
sergiopaniego Jul 10, 2025
7d0d001
Merge branch 'main' into grpo_vlm
kashif Jul 11, 2025
41d1e11
formatting
kashif Jul 11, 2025
a34b5f7
log probs now use visual context
kashif Jul 11, 2025
a619898
remove duplicate import
kashif Jul 11, 2025
1c02bb9
link example script
kashif Jul 11, 2025
5b3eaf7
use AutoProcessor for vision_models
kashif Jul 11, 2025
215e6fb
split_tensor_dict and split_tensor_dict handle BatchFeature objects
kashif Jul 11, 2025
99da8e8
Only shuffle/split tensors that have the expected batch dimension
kashif Jul 11, 2025
3eb777a
Script more general
sergiopaniego Jul 11, 2025
ed17dff
Merge branch 'grpo_vlm' of github.com:CompN3rd/trl into grpo_vlm
sergiopaniego Jul 11, 2025
f45f63b
Merge branch 'main' of github.com:huggingface/trl into grpo_vlm
sergiopaniego Jul 11, 2025
e6c983b
fix _get_per_token_logps_and_entropies
kashif Jul 11, 2025
eb9e156
formatting
kashif Jul 11, 2025
a1507fc
add _prepare_inputs_vlm_mode and move slow tests to test_grpo_slow.py
kashif Jul 11, 2025
0aba452
add back the tests
kashif Jul 11, 2025
a7b35ef
formatting
kashif Jul 12, 2025
bd65822
Merge branch 'main' into grpo_vlm
kashif Jul 12, 2025
4c2bac5
formatting
kashif Jul 12, 2025
ce3a7ac
fix tests
kashif Jul 12, 2025
fe9d5ee
Update docs/source/grpo_trainer.md
kashif Jul 12, 2025
e2c17ca
Update docs/source/grpo_trainer.md
kashif Jul 12, 2025
621d193
use isinstance BatchFeature
kashif Jul 12, 2025
5f49c97
removed unused code
kashif Jul 12, 2025
a82e432
Update trl/trainer/grpo_trainer.py
kashif Jul 12, 2025
6c3d94b
make get_from_processor_or_tokenizer a function
kashif Jul 12, 2025
f0355d1
add a setter
kashif Jul 12, 2025
f48d0f6
remove unneeded test
kashif Jul 12, 2025
6b0be5c
assume normal configurations
kashif Jul 12, 2025
ac8c6c4
review
kashif Jul 12, 2025
81cb565
fix test
kashif Jul 14, 2025
cfd0844
skip test if no vllm
kashif Jul 14, 2025
3a9f16a
integrate fixes from #3725
kashif Jul 14, 2025
f57b98a
fix log sizes
kashif Jul 14, 2025
c5d0a1a
Update tests/test_grpo_trainer.py
kashif Jul 14, 2025
5e791c7
undo changes in tests and move vllm test to slow
kashif Jul 15, 2025
56dc897
validate_and_preprocess_images to utils
kashif Jul 15, 2025
4729cdc
Updated docs and example
sergiopaniego Jul 15, 2025
873821a
Update tests/slow/test_grpo_slow.py
kashif Jul 15, 2025
784c265
Update tests/slow/test_grpo_slow.py
kashif Jul 15, 2025
368b751
Update tests/slow/test_grpo_slow.py
kashif Jul 15, 2025
454a4d6
fix import
kashif Jul 15, 2025
a8e516f
Update import
sergiopaniego Jul 15, 2025
5e3953d
fix repeat_count
kashif Jul 15, 2025
3c9091e
fake_generate takes input_ids
kashif Jul 15, 2025
4102d0f
Merge branch 'main' into grpo_vlm
kashif Jul 15, 2025
64dfa81
formatting
kashif Jul 15, 2025
fa2d349
Merge branch 'main' into grpo_vlm
qgallouedec Jul 16, 2025
4a98b10
tiny qwen vl
qgallouedec Jul 16, 2025
348adf3
Merge branch 'main' into grpo_vlm
kashif Jul 16, 2025
aac9f44
Merge branch 'main' into grpo_vlm
qgallouedec Jul 19, 2025
408ed3f
zen image initial version
qgallouedec Jul 19, 2025
8db1520
generate tiny
qgallouedec Jul 19, 2025
5a576c8
fix get_from_processor_or_tokenizer, dtype and model loading
qgallouedec Jul 19, 2025
5807aa5
revert unrelated change
qgallouedec Jul 19, 2025
73eddf1
fix tests
kashif Jul 19, 2025
5b6fa17
Merge branch 'main' into grpo_vlm
qgallouedec Jul 19, 2025
2b79b4b
Merge branch 'main' into grpo_vlm
qgallouedec Jul 19, 2025
1a656f7
finally it works!
qgallouedec Jul 20, 2025
1c9093c
doc fix
qgallouedec Jul 20, 2025
daf4b32
clean import
qgallouedec Jul 20, 2025
ab0ff92
generate tiny models
qgallouedec Jul 20, 2025
994fd91
let's go
qgallouedec Jul 20, 2025
07dbb5c
Update grpo_trainer.py
qgallouedec Jul 20, 2025
83e9aae
vlm peft
qgallouedec Jul 20, 2025
d31022f
solved
qgallouedec Jul 20, 2025
f43998c
fix breaking test (inc. slow)
kashif Jul 20, 2025
a8191ae
Merge branch 'main' into grpo_vlm
kashif Jul 20, 2025
d677874
test qwenv2, IS and liger
qgallouedec Jul 20, 2025
d0d9044
liger
qgallouedec Jul 20, 2025
95357d2
fix generate tiny
qgallouedec Jul 20, 2025
5976ad2
clean test
qgallouedec Jul 20, 2025
f14401a
fix comment
qgallouedec Jul 20, 2025
e08d342
fix doc
qgallouedec Jul 20, 2025
20f4d14
fix doc
qgallouedec Jul 20, 2025
dc66e72
add test for training with VLM and prompt truncation
qgallouedec Jul 20, 2025
48ae448
torchvision
qgallouedec Jul 20, 2025
f4733ac
add logic to truncate prompt ids if max_prompt_length is set
kashif Jul 20, 2025
7f9c835
vllm server support images!
qgallouedec Jul 21, 2025
b122161
fix vision import
qgallouedec Jul 21, 2025
00d3496
vllm image support
qgallouedec Jul 21, 2025
78f4ecb
clean truncation
qgallouedec Jul 21, 2025
b754d61
log images
qgallouedec Jul 21, 2025
60c14b0
clean
qgallouedec Jul 21, 2025
13e8ac1
Add support for Gemma3 model and update GRPOTrainer initialization
qgallouedec Jul 22, 2025
fe91966
style
qgallouedec Jul 22, 2025
6378887
doc simplification
qgallouedec Jul 22, 2025
1935a02
simplify example script
qgallouedec Jul 22, 2025
1fbf33a
Enhance GRPO training script with additional parameters and improve p…
qgallouedec Jul 22, 2025
488eb16
Merge branch 'main' into grpo_vlm
qgallouedec Jul 22, 2025
f5f164b
Update trl/scripts/vllm_serve.py
kashif Jul 22, 2025
1423e29
Merge branch 'main' into grpo_vlm
kashif Jul 22, 2025
d8460ea
Update trl/scripts/vllm_serve.py
kashif Jul 22, 2025
1b757c1
Update trl/trainer/grpo_trainer.py
qgallouedec Jul 22, 2025
b78af71
Add tests for split and unsplit pixel values by grid functionality
qgallouedec Jul 22, 2025
578ca66
Merge branch 'grpo_vlm' of https://github.com/compn3rd/trl into grpo_vlm
qgallouedec Jul 22, 2025
94f5be5
add a comment
qgallouedec Jul 22, 2025
782f99e
require vision for some tests
qgallouedec Jul 22, 2025
b264b29
Update tests/test_grpo_trainer.py
qgallouedec Jul 22, 2025
5b69239
fix require vision
qgallouedec Jul 22, 2025
52a1f29
Merge branch 'grpo_vlm' of https://github.com/compn3rd/trl into grpo_vlm
qgallouedec Jul 22, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 60 additions & 2 deletions docs/source/grpo_trainer.md
Original file line number Diff line number Diff line change
Expand Up @@ -228,7 +228,7 @@ training_args = GRPOConfig(

Depending on the model size and the overall GPU memory requirements for training, you may need to adjust the `vllm_gpu_memory_utilization` parameter in [`GRPOConfig`] to avoid underutilization or out-of-memory errors.

We provide a [small script](https://huggingface.co/spaces/trl-lib/recommend-vllm-memory) to help estimate the recommended GPU memory utilization based on your model configuration and experiment settings. Simply use it as follows to get `vllm_gpu_memory_utilization` recommendation:
We provide a [HF Space](https://huggingface.co/spaces/trl-lib/recommend-vllm-memory) to help estimate the recommended GPU memory utilization based on your model configuration and experiment settings. Simply use it as follows to get `vllm_gpu_memory_utilization` recommendation:

<iframe
src="https://trl-lib-recommend-vllm-memory.hf.space"
Expand All @@ -242,7 +242,9 @@ If the recommended value does not work in your environment, we suggest adding a
</Tip>

<Tip>

By default, GRPO uses `MASTER_ADDR=localhost` and `MASTER_PORT=12345` for vLLM, but you can override these values by setting the environment variables accordingly.

</Tip>

For more information, see [Speeding up training with vLLM](speeding_up_training#vllm-for-fast-generation-in-online-methods).
Expand Down Expand Up @@ -329,7 +331,7 @@ The [`GRPOTrainer`] supports using custom reward functions instead of dense rewa
- `prompts` (contains the prompts),
- `completions` (contains the generated completions),
- `completions_ids` (contains the tokenized completions),
- `trainer_state` ([`transformers.TrainerState`]): The current state of the trainer. This can be used to implement dynamic reward functions, such as curriculum learning, where the reward is adjusted based on the training progress. For more details on the available attributes, refer to the [`TrainerState`](https://huggingface.co/docs/transformers/main/en/main_classes/callback#transformers.TrainerState) documentation.
- `trainer_state` ([`~transformers.TrainerState`]): The current state of the trainer. This can be used to implement dynamic reward functions, such as curriculum learning, where the reward is adjusted based on the training progress.
- All columns names (but `prompt`) that the dataset may have. For example, if the dataset contains a column named `ground_truth`, the function will be called with `ground_truth` as a keyword argument.

The easiest way to comply with this requirement is to use `**kwargs` in the function signature.
Expand Down Expand Up @@ -520,10 +522,66 @@ trainer = GRPOTrainer(
...,
)
```

and the reward will be computed as the sum of the rewards from each function, or the weighted sum if `reward_weights` is provided in the config.

Note that [`GRPOTrainer`] supports multiple reward functions of different types. See the parameters documentation for more details.

## Vision-Language Model (VLM) Training

GRPO supports training Vision-Language Models (VLMs) on multimodal datasets containing both text and images.

### Supported Models

Tested with:

- **Qwen2.5-VL** — e.g., `Qwen/Qwen2.5-VL-3B-Instruct`
- **Qwen2-VL** — e.g., `Qwen/Qwen2-VL-2B-Instruct`
- **Gemma3** — e.g., `google/gemma-3-4b-it`

<Tip>
Compatibility with all VLMs is not guaranteed. If you believe a model should be supported, feel free to open an issue on GitHub — or better yet, submit a pull request with the required changes.
</Tip>

### Quick Start

Use [grpo\_vlm.py](https://github.com/huggingface/trl/blob/main/examples/scripts/grpo_vlm.py) to fine-tune a VLM. Example command for training on [`lmms-lab/multimodal-open-r1-8k-verified`](https://huggingface.co/datasets/lmms-lab/multimodal-open-r1-8k-verified):

```bash
accelerate launch \
--config_file=examples/accelerate_configs/deepspeed_zero3.yaml \
examples/scripts/grpo_vlm.py \
--model_name_or_path Qwen/Qwen2.5-VL-3B-Instruct \
--output_dir grpo-Qwen2.5-VL-3B-Instruct \
--learning_rate 1e-5 \
--gradient_checkpointing \
--torch_dtype bfloat16 \
--max_prompt_length 2048 \
--max_completion_length 1024 \
--use_vllm \
--vllm_mode colocate \
--use_peft \
--lora_target_modules "q_proj", "v_proj" \
--log_completions
```

### Configuration Tips

- Use LoRA on vision-language projection layers
- VLM may require a lot of image tokens, which cannot be truncated. Set `max_prompt_length` to a higher value (e.g., 2048 in the above example) to accommodate longer prompts.
- Enable 4-bit quantization to reduce memory usage
- VLMs are memory-intensive — start with smaller batch sizes
- Most models are compatible with vLLM (`server` and `colocate` modes)

### Dataset Format

Each training sample should include:

- `prompt`: Text formatted via the processor's chat template
- `image`: A single image (PIL or NumPy array)

The trainer automatically handles image-to-tensor conversion via the model’s image processor.

## GRPOTrainer

[[autodoc]] GRPOTrainer
Expand Down
168 changes: 168 additions & 0 deletions examples/scripts/grpo_vlm.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
# Copyright 2020-2025 The HuggingFace Team. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""
pip install math_verify

accelerate launch \
--config_file=examples/accelerate_configs/deepspeed_zero3.yaml \
examples/scripts/grpo_vlm.py \
--model_name_or_path Qwen/Qwen2.5-VL-3B-Instruct \
--output_dir grpo-Qwen2.5-VL-3B-Instruct \
--learning_rate 1e-5 \
--gradient_checkpointing \
--torch_dtype bfloat16 \
--max_prompt_length 2048 \
--max_completion_length 1024 \
--use_vllm \
--vllm_mode colocate \
--use_peft \
--lora_target_modules "q_proj", "v_proj" \
--log_completions
"""

import torch
from datasets import load_dataset
from latex2sympy2_extended import NormalizationConfig
from math_verify import LatexExtractionConfig, parse, verify

from trl import (
GRPOConfig,
GRPOTrainer,
ModelConfig,
ScriptArguments,
TrlParser,
get_kbit_device_map,
get_peft_config,
get_quantization_config,
)
from trl.rewards import think_format_reward


if __name__ == "__main__":
parser = TrlParser((ScriptArguments, GRPOConfig, ModelConfig))
script_args, training_args, model_args = parser.parse_args_and_config()
################
# Model & Processor
################
torch_dtype = (
model_args.torch_dtype if model_args.torch_dtype in ["auto", None] else getattr(torch, model_args.torch_dtype)
)
quantization_config = get_quantization_config(model_args)
model_kwargs = dict(
revision=model_args.model_revision,
attn_implementation=model_args.attn_implementation,
torch_dtype=torch_dtype,
device_map=get_kbit_device_map() if quantization_config is not None else None,
quantization_config=quantization_config,
)

################
# Dataset
################
dataset = load_dataset("lmms-lab/multimodal-open-r1-8k-verified", split="train")
dataset = dataset.train_test_split(test_size=100, seed=42)

SYSTEM_PROMPT = (
"A conversation between user and assistant. The user asks a question, and the assistant solves it. The "
"assistant first thinks about the reasoning process in the mind and then provides the user with the answer. "
"The reasoning process and answer are enclosed within <think></think> tags, i.e., <think>\nThis is my "
"reasoning.\n</think>\nThis is my answer."
)

def make_conversation(example):
prompt = [
{"role": "system", "content": SYSTEM_PROMPT},
{"role": "user", "content": example["problem"]},
]
return {"prompt": prompt}

dataset = dataset.map(make_conversation)

# Filter have big images
def filter_big_images(example):
image = example["image"]
return image.size[0] < 512 and image.size[1] < 512

dataset = dataset.filter(filter_big_images)

train_dataset = dataset["train"]
eval_dataset = dataset["test"] if training_args.eval_strategy != "no" else None

################
# Reward Function for Training
################
def accuracy_reward(completions, solution: list[str], **kwargs):
"""Reward function that checks if the completion matches the ground truth.
- If both gold and prediction are parseable → use math verification.
- If not parseable → compare as normalized text.
"""
rewards = []
contents = [completion[0]["content"] for completion in completions]
for content, sol in zip(contents, solution):
try:
gold_parsed = parse(sol, extraction_mode="first_match")
except Exception:
gold_parsed = []

if len(gold_parsed) != 0:
# Try parsing predicted answer too
try:
answer_parsed = parse(
content,
extraction_config=[
LatexExtractionConfig(
normalization_config=NormalizationConfig(
nits=False,
malformed_operators=False,
basic_latex=True,
boxed="all",
units=True,
),
boxed_match_priority=0,
try_extract_without_anchor=False,
)
],
extraction_mode="first_match",
)
reward = float(verify(gold_parsed, answer_parsed))
except Exception as e:
print(f"verify failed: {e}, answer: {content}, gold: {sol}")
reward = None
else:
# fallback to text match
reward = float(content.strip().lower() == sol.strip().lower())

rewards.append(reward)

return rewards

################
# Training
################
trainer = GRPOTrainer(
model=model_args.model_name_or_path,
args=training_args,
reward_funcs=[think_format_reward, accuracy_reward],
train_dataset=train_dataset,
eval_dataset=eval_dataset,
peft_config=get_peft_config(model_args),
)

trainer.train()

# Save and push to hub
trainer.save_model(training_args.output_dir)
if training_args.push_to_hub:
trainer.push_to_hub(dataset_name=script_args.dataset_name)
46 changes: 29 additions & 17 deletions scripts/generate_tiny_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@
BartModel,
BloomConfig,
BloomForCausalLM,
CLIPVisionConfig,
CohereConfig,
CohereForCausalLM,
DbrxConfig,
Expand All @@ -35,6 +34,8 @@
FalconMambaForCausalLM,
Gemma2Config,
Gemma2ForCausalLM,
Gemma3Config,
Gemma3ForConditionalGeneration,
GemmaConfig,
GemmaForCausalLM,
GPT2Config,
Expand All @@ -58,19 +59,21 @@
PaliGemmaForConditionalGeneration,
Phi3Config,
Phi3ForCausalLM,
Qwen2_5_VLConfig,
Qwen2_5_VLForConditionalGeneration,
Qwen2Config,
Qwen2ForCausalLM,
Qwen2ForSequenceClassification,
Qwen2VLConfig,
Qwen2VLForConditionalGeneration,
Qwen3Config,
Qwen3ForCausalLM,
Qwen3ForSequenceClassification,
Qwen3MoeConfig,
Qwen3MoeForCausalLM,
SiglipVisionConfig,
T5Config,
T5ForConditionalGeneration,
)
from transformers.models.idefics2.configuration_idefics2 import Idefics2VisionConfig


ORGANIZATION = "trl-internal-testing"
Expand Down Expand Up @@ -211,8 +214,8 @@ def push_to_hub(model, tokenizer, prefix=None, suffix=None):

# Encoder-decoder models
for model_id, config_class, model_class, suffix in [
("google/flan-t5-small", T5Config, T5ForConditionalGeneration, None),
("facebook/bart-base", BartConfig, BartModel, None),
("google/flan-t5-small", T5Config, T5ForConditionalGeneration, None),
]:
tokenizer = AutoTokenizer.from_pretrained(model_id)
config = config_class(
Expand All @@ -232,35 +235,44 @@ def push_to_hub(model, tokenizer, prefix=None, suffix=None):


# Vision Language Models
# fmt: off
for model_id, config_class, text_config_class, vision_config_class, model_class in [
("HuggingFaceM4/idefics2-8b", Idefics2Config, MistralConfig, Idefics2VisionConfig, Idefics2ForConditionalGeneration),
("llava-hf/llava-1.5-7b-hf", LlavaConfig, LlamaConfig, CLIPVisionConfig, LlavaForConditionalGeneration),
("llava-hf/llava-v1.6-mistral-7b-hf", LlavaNextConfig, MistralConfig, CLIPVisionConfig, LlavaNextForConditionalGeneration),
("google/paligemma-3b-pt-224", PaliGemmaConfig, GemmaConfig, SiglipVisionConfig, PaliGemmaForConditionalGeneration),
for model_id, config_class, model_class in [
("google/gemma-3-4b-it", Gemma3Config, Gemma3ForConditionalGeneration),
("google/paligemma-3b-pt-224", PaliGemmaConfig, PaliGemmaForConditionalGeneration),
("HuggingFaceM4/idefics2-8b", Idefics2Config, Idefics2ForConditionalGeneration),
("llava-hf/llava-1.5-7b-hf", LlavaConfig, LlavaForConditionalGeneration),
("llava-hf/llava-v1.6-mistral-7b-hf", LlavaNextConfig, LlavaNextForConditionalGeneration),
("Qwen/Qwen2-VL-2B-Instruct", Qwen2VLConfig, Qwen2VLForConditionalGeneration),
("Qwen/Qwen2.5-VL-3B-Instruct", Qwen2_5_VLConfig, Qwen2_5_VLForConditionalGeneration),
]:
# fmt: on
processor = AutoProcessor.from_pretrained(model_id)
kwargs = {}
text_kwargs = {}
vision_kwargs = {}
if config_class == PaliGemmaConfig:
kwargs["projection_dim"] = 8
vision_kwargs = {}
if vision_config_class in [CLIPVisionConfig, SiglipVisionConfig]:
if config_class in [LlavaConfig, LlavaNextConfig, PaliGemmaConfig]:
vision_kwargs["projection_dim"] = 8
if vision_config_class == CLIPVisionConfig:
if config_class in [LlavaConfig, LlavaNextConfig]:
vision_kwargs["image_size"] = 336
vision_kwargs["patch_size"] = 14
if config_class in [Qwen2VLConfig, Qwen2_5_VLConfig]:
kwargs["vision_start_token_id"] = 151652
text_kwargs["rope_scaling"] = {"type": "mrope", "mrope_section": [1]}
vision_kwargs["depth"] = 4
vision_kwargs["embed_dim"] = 64

config = config_class(
text_config=text_config_class(
text_config=dict(
vocab_size=processor.tokenizer.vocab_size + len(processor.tokenizer.added_tokens_encoder),
hidden_size=8,
num_attention_heads=4,
num_key_value_heads=2,
num_hidden_layers=2,
intermediate_size=32,
**text_kwargs,
),
vision_config=vision_config_class(
hidden_size=8,
vision_config=dict(
hidden_size=16,
num_attention_heads=4,
num_hidden_layers=2,
intermediate_size=32,
Expand Down
Loading
Loading