Spaces:
Runtime error
Runtime error
| import json | |
| import os | |
| from typing import TYPE_CHECKING, Dict, List, Optional, Tuple, Union | |
| import torch | |
| from transformers import Trainer | |
| from ...extras.logging import get_logger | |
| if TYPE_CHECKING: | |
| from transformers.modeling_utils import PreTrainedModel | |
| from transformers.trainer import PredictionOutput | |
| logger = get_logger(__name__) | |
| class PairwiseTrainer(Trainer): | |
| r""" | |
| Inherits PeftTrainer to compute pairwise loss. | |
| """ | |
| def __init__(self, *args, **kwargs): | |
| super().__init__(*args, **kwargs) | |
| self.can_return_loss = True # override property to return eval_loss | |
| def compute_loss( | |
| self, model: "PreTrainedModel", inputs: Dict[str, torch.Tensor], return_outputs: Optional[bool] = False | |
| ) -> Union[torch.Tensor, Tuple[torch.Tensor, List[torch.Tensor]]]: | |
| r""" | |
| Computes pairwise loss. The first n examples are chosen and the last n examples are rejected. | |
| Subclass and override to inject custom behavior. | |
| Note that the first element will be removed from the output tuple. | |
| See: https://github.com/huggingface/transformers/blob/v4.30.2/src/transformers/trainer.py#L3509 | |
| """ | |
| # Compute rewards | |
| _, _, values = model(**inputs, output_hidden_states=True, return_dict=True) | |
| unwrapped_model: "PreTrainedModel" = self.accelerator.unwrap_model(self.model) | |
| if getattr(unwrapped_model.config, "model_type", None) == "chatglm": | |
| values = torch.transpose(values, 0, 1) | |
| # Split the inputs and rewards into two parts, chosen and rejected | |
| batch_size = inputs["input_ids"].size(0) // 2 | |
| chosen_input_ids, rejected_input_ids = inputs["input_ids"][:batch_size], inputs["input_ids"][batch_size:] | |
| chosen_rewards, rejected_rewards = values[:batch_size], values[batch_size:] | |
| chosen_scores, rejected_scores = [], [] | |
| # Compute pairwise loss. Only backprop on the different tokens before padding | |
| # Inspired by: https://github.com/CarperAI/trlx/blob/main/examples/summarize_rlhf/reward_model/reward_model.py | |
| loss = 0 | |
| for i in range(batch_size): | |
| chosen_length = (chosen_input_ids[i] != self.tokenizer.pad_token_id).nonzero()[-1] + 1 | |
| rejected_length = (rejected_input_ids[i] != self.tokenizer.pad_token_id).nonzero()[-1] + 1 | |
| check_divergence = (chosen_input_ids[i] != rejected_input_ids[i]).nonzero() | |
| if len(check_divergence) == 0: | |
| end_index = chosen_length | |
| div_index = end_index - 1 | |
| else: | |
| end_index = max(chosen_length, rejected_length) | |
| div_index = check_divergence[0] | |
| assert div_index > 0 | |
| chosen_trunc_rewards = chosen_rewards[i, div_index:end_index] | |
| rejected_trunc_rewards = rejected_rewards[i, div_index:end_index] | |
| if return_outputs: # use the score on the last token except pad token for inference | |
| chosen_scores.append(chosen_rewards[i, chosen_length - 1]) | |
| rejected_scores.append(rejected_rewards[i, rejected_length - 1]) | |
| loss += -torch.nn.functional.logsigmoid(chosen_trunc_rewards - rejected_trunc_rewards).mean() | |
| loss = loss / batch_size | |
| if return_outputs: | |
| chosen_scores, rejected_scores = torch.stack(chosen_scores), torch.stack(rejected_scores) | |
| return loss, [loss, chosen_scores, rejected_scores] | |
| return loss | |
| def save_predictions(self, predict_results: "PredictionOutput") -> None: | |
| r""" | |
| Saves model predictions to `output_dir`. | |
| A custom behavior that not contained in Seq2SeqTrainer. | |
| """ | |
| if not self.is_world_process_zero(): | |
| return | |
| output_prediction_file = os.path.join(self.args.output_dir, "generated_predictions.jsonl") | |
| logger.info(f"Saving prediction results to {output_prediction_file}") | |
| chosen_scores, rejected_scores = predict_results.predictions | |
| with open(output_prediction_file, "w", encoding="utf-8") as writer: | |
| res: List[str] = [] | |
| for c_score, r_score in zip(chosen_scores, rejected_scores): | |
| res.append(json.dumps({"chosen": round(float(c_score), 2), "rejected": round(float(r_score), 2)})) | |
| writer.write("\n".join(res)) | |