# Copyright (c) 2023 PaddlePaddle Authors. 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. from collections import defaultdict import paddle from paddlenlp.trainer import Trainer class RewardTrainer(Trainer): """ Initialize RewardTrainer. """ def __init__(self, model, data_collator, process_reward=False, **kwargs): super().__init__(model, data_collator=data_collator, **kwargs) self._stored_metrics = defaultdict(lambda: defaultdict(list)) if self.compute_metrics is not None: raise NotImplementedError("compute_metrics is not supported for RewardTrainer") self.process_reward = process_reward def get_batch_metrics(self, model, batch, train_eval="train"): """Compute the RM loss and other metrics for the given batch of inputs for train or test.""" if not self.process_reward: rm_inputs = { "input_ids": batch["input_ids"], "position_ids": batch["position_ids"], "response_indexs": batch["response_indexs"], } else: rm_inputs = { "input_ids": batch["input_ids"], "position_ids": batch["position_ids"], "labels": batch["labels"], } if "attention_mask" in batch: rm_inputs["attention_mask"] = batch["attention_mask"] elif "attn_mask_start_row_indices" in batch: rm_inputs["attn_mask_start_row_indices"] = batch["attn_mask_start_row_indices"] elif "attn_mask_startend_row_indices" in batch: rm_inputs["attn_mask_startend_row_indices"] = batch["attn_mask_startend_row_indices"] prefix = "eval_" if train_eval == "eval" else "" metrics = {} if not self.process_reward: loss, chosen_scores, rejected_scores = model(**rm_inputs) metrics[f"{prefix}accuracy"] = (chosen_scores > rejected_scores).astype("float32").mean() else: # PRM Loss loss, accs = model(**rm_inputs) metrics[f"{prefix}accuracy"] = accs.astype("float32").mean() for key in metrics: metrics[key] = self._nested_gather(paddle.tile(metrics[key], repeat_times=[1, 1])).mean().cpu() if self.args.should_save: self.store_metrics(metrics, train_eval=train_eval) return loss def compute_loss(self, model, inputs): """Compute the loss for the given batch of inputs.""" loss = self.get_batch_metrics(model, inputs, train_eval="train") return loss def prediction_step(self, model, inputs, prediction_loss_only=False, ignore_keys=None): """prediction_step""" if ignore_keys is None: if hasattr(model, "config"): ignore_keys = getattr(model.config, "keys_to_ignore_at_inference", []) else: ignore_keys = [] with paddle.no_grad(): with self.autocast_smart_context_manager(): loss = self.get_batch_metrics(model, inputs, train_eval="eval") if prediction_loss_only: return (loss.detach(), None, None) else: raise NotImplementedError("RewardTrainer only supports prediction_loss_only=True for now.") def store_metrics(self, metrics, train_eval="train"): """store_metrics""" for key, value in metrics.items(): self._stored_metrics[train_eval][key].append(value) def log(self, logs, **kwargs): """ Log `logs` on the various objects watching training, including stored metrics. Args: logs (`Dict[str, float]`): The values to log. """ # logs either has 'loss' or 'eval_loss' train_eval = "train" if "loss" in logs else "eval" # Add averaged stored metrics to logs for key, metrics in self._stored_metrics[train_eval].items(): logs[key] = paddle.to_tensor(metrics).mean().item() del self._stored_metrics[train_eval] if self.state.epoch is not None and train_eval == "train": self.state.epoch *= self.args.num_train_epochs return super().log(logs, **kwargs)