1313# limitations under the License.
1414
1515"""
16- Supervised fine-tuning script for decoder language models.
16+ Supervised fine-tuning script for decoder language models and vision-language models .
1717
1818Usage:
1919
3939
4040import datasets
4141import transformers
42- from transformers import set_seed
42+ from transformers import set_seed , AutoModelForVision2Seq , AutoProcessor , LlavaForConditionalGeneration
4343from transformers .trainer_utils import get_last_checkpoint
4444from trl import ModelConfig , SFTTrainer , TrlParser , get_peft_config , setup_chat_format
4545
4646from open_r1 .configs import ScriptArguments , SFTConfig
47- from open_r1 .utils import get_dataset , get_model , get_tokenizer
47+ from open_r1 .utils import get_dataset , get_model , get_tokenizer , get_processor
4848from open_r1 .utils .callbacks import get_callbacks
4949from open_r1 .utils .wandb_logging import init_wandb_training
5050
5151logger = logging .getLogger (__name__ )
5252
5353
54+ def create_vlm_collate_fn (processor ):
55+ """Create a data collator for VLM training that handles images and text."""
56+
57+ def collate_fn (examples ):
58+ # Get the texts and images, and apply the chat template
59+ texts = [processor .apply_chat_template (example ["messages" ], tokenize = False ) for example in examples ]
60+ images = [example ["images" ] for example in examples ]
61+
62+ # Handle LLaVA 1.5 which doesn't support multiple images
63+ if isinstance (processor .model , LlavaForConditionalGeneration ):
64+ images = [image [0 ] if image else None for image in images ]
65+
66+ # Tokenize the texts and process the images
67+ batch = processor (text = texts , images = images , return_tensors = "pt" , padding = True )
68+
69+ # The labels are the input_ids, and we mask the padding tokens in the loss computation
70+ labels = batch ["input_ids" ].clone ()
71+ labels [labels == processor .tokenizer .pad_token_id ] = - 100
72+
73+ # Ignore the image token index in the loss computation (model specific)
74+ if hasattr (processor , 'image_token' ):
75+ image_token_id = processor .tokenizer .convert_tokens_to_ids (processor .image_token )
76+ labels [labels == image_token_id ] = - 100
77+
78+ batch ["labels" ] = labels
79+ return batch
80+
81+ return collate_fn
82+
83+
5484def main (script_args , training_args , model_args ):
5585 set_seed (training_args .seed )
5686
@@ -84,29 +114,54 @@ def main(script_args, training_args, model_args):
84114 init_wandb_training (training_args )
85115
86116 ######################################
87- # Load dataset, tokenizer, and model #
117+ # Load dataset, processor/ tokenizer, and model #
88118 ######################################
89119 dataset = get_dataset (script_args )
90- tokenizer = get_tokenizer (model_args , training_args )
91- model = get_model (model_args , training_args )
92120
93- if tokenizer .chat_template is None :
94- logger .info ("No chat template provided, defaulting to ChatML." )
95- model , tokenizer = setup_chat_format (model , tokenizer , format = "chatml" )
121+ if training_args .vision_model :
122+ logger .info ("Setting up vision-language model training" )
123+
124+ # Set VLM-specific training arguments (following TRL reference)
125+ training_args .gradient_checkpointing_kwargs = dict (use_reentrant = False )
126+ training_args .remove_unused_columns = False
127+ training_args .dataset_kwargs = {"skip_prepare_dataset" : True }
128+
129+ # Load processor and model for VLM
130+ processor = get_processor (model_args , training_args )
131+ model = get_model (model_args , training_args ) # This should return AutoModelForVision2Seq
132+ data_collator = create_vlm_collate_fn (processor )
133+ processing_class = processor .tokenizer
134+ model_tags = ["open-r1" , "vision-language" , "vlm" ]
135+
136+ else :
137+ logger .info ("Setting up text-only model training" )
138+
139+ # Load tokenizer and model for text-only
140+ tokenizer = get_tokenizer (model_args , training_args )
141+ model = get_model (model_args , training_args )
142+
143+ if tokenizer .chat_template is None :
144+ logger .info ("No chat template provided, defaulting to ChatML." )
145+ model , tokenizer = setup_chat_format (model , tokenizer , format = "chatml" )
146+
147+ data_collator = None # Use default
148+ processing_class = tokenizer
149+ model_tags = ["open-r1" ]
96150
97151 ############################
98152 # Initialize the SFT Trainer
99153 ############################
100154 trainer = SFTTrainer (
101155 model = model ,
102156 args = training_args ,
157+ data_collator = data_collator ,
103158 train_dataset = dataset [script_args .dataset_train_split ],
104159 eval_dataset = (
105160 dataset [script_args .dataset_test_split ]
106161 if training_args .eval_strategy != "no"
107162 else None
108163 ),
109- processing_class = tokenizer ,
164+ processing_class = processing_class ,
110165 peft_config = get_peft_config (model_args ),
111166 callbacks = get_callbacks (training_args , model_args ),
112167 )
@@ -131,16 +186,13 @@ def main(script_args, training_args, model_args):
131186 # Save model and create model card
132187 ##################################
133188 logger .info ("*** Save model ***" )
134- # Align the model's generation config with the tokenizer's eos token
135- # to avoid unbounded generation in the transformers `pipeline()` function
136- trainer .model .generation_config .eos_token_id = tokenizer .eos_token_id
137189 trainer .save_model (training_args .output_dir )
138190 logger .info (f"Model saved to { training_args .output_dir } " )
139191
140192 # Save everything else on main process
141193 kwargs = {
142194 "dataset_name" : script_args .dataset_name ,
143- "tags" : [ "open-r1" ] ,
195+ "tags" : model_tags ,
144196 }
145197 if trainer .accelerator .is_main_process :
146198 trainer .create_model_card (** kwargs )
@@ -164,6 +216,9 @@ def main(script_args, training_args, model_args):
164216 if training_args .push_to_hub :
165217 logger .info ("Pushing to hub..." )
166218 trainer .push_to_hub (** kwargs )
219+ # Also push processor for VLM models
220+ if training_args .vision_model and trainer .accelerator .is_main_process :
221+ processor .push_to_hub (training_args .hub_model_id )
167222
168223
169224if __name__ == "__main__" :
0 commit comments