| 80 | |
| 81 | |
| 82 | class SequentialFunctionRewardManager(FunctionRewardManager): |
| 83 | reward_fn: SequentialRewardFunction |
| 84 | |
| 85 | def compute_reward(self, data: DataProto) -> Tuple[torch.Tensor, Dict[str, List[float]]]: |
| 86 | reward_tensor = torch.zeros_like(data.batch["responses"], dtype=torch.float32) |
| 87 | reward_metrics = defaultdict(list) |
| 88 | response_ids = data.batch["responses"] |
| 89 | response_length = data.batch["response_mask"].sum(dim=-1) |
| 90 | |
| 91 | # pull arrays once |
| 92 | gt_arr = data.non_tensor_batch["ground_truth"] |
| 93 | qn_arr = data.non_tensor_batch.get("question", None) |
| 94 | desc_arr = data.non_tensor_batch.get("description_answers", None) |
| 95 | # support either `images` or `image` in upstream payload |
| 96 | img_arr = ( |
| 97 | data.non_tensor_batch.get("images", None) |
| 98 | if "images" in data.non_tensor_batch |
| 99 | else data.non_tensor_batch.get("image", None) |
| 100 | ) |
| 101 | |
| 102 | # inspect parameters for flexible calling |
| 103 | target_fn = self.reward_fn.func if isinstance(self.reward_fn, partial) else self.reward_fn |
| 104 | param_names = list(inspect.signature(target_fn).parameters.keys()) |
| 105 | |
| 106 | for i in range(len(data)): |
| 107 | valid_response_ids = response_ids[i][: response_length[i]] |
| 108 | response_str = self.tokenizer.decode( |
| 109 | valid_response_ids, skip_special_tokens=self.config.skip_special_tokens |
| 110 | ) |
| 111 | ground_truth = gt_arr[i] |
| 112 | question = None if qn_arr is None else qn_arr[i] |
| 113 | description_answer = "" if desc_arr is None else desc_arr[i] |
| 114 | image_val = None if img_arr is None else img_arr[i] |
| 115 | |
| 116 | base_args = [response_str, ground_truth] |
| 117 | call_kwargs = {} |
| 118 | # prefer kwargs when the function advertises them |
| 119 | if question is not None and "question" in param_names: |
| 120 | call_kwargs["question"] = question |
| 121 | if "description_answers" in param_names: |
| 122 | call_kwargs["description_answers"] = description_answer |
| 123 | if "images" in param_names: |
| 124 | call_kwargs["images"] = image_val |
| 125 | elif "image" in param_names: |
| 126 | call_kwargs["image"] = image_val |
| 127 | |
| 128 | try: |
| 129 | score = self.reward_fn(*base_args, **call_kwargs) |
| 130 | except TypeError: |
| 131 | # fallback 1: include question as positional third |
| 132 | try: |
| 133 | if question is None: |
| 134 | raise TypeError |
| 135 | score = self.reward_fn(response_str, ground_truth, question) |
| 136 | except TypeError: |
| 137 | # fallback 2: include description and image positionally if accepted |
| 138 | try: |
| 139 | score = self.reward_fn( |
nothing calls this directly
no outgoing calls
no test coverage detected