def forward(self, model, sample, reduce=True, split="valid"):
        """Compute the loss for the given sample.

        Returns a tuple with three elements:
        1) the loss
        2) the sample size, which is used as the denominator for the gradient
        3) logging outputs to display while training
        """
        if not self.is_generation:
            option_num = self.task.fewshot_task.class_num
            assert sample["targets"].size(0) % option_num == 0
            sample_size = sample["targets"].size(0) // option_num
            loss_mask = sample["net_input"]["gpt_loss_mask"][:, 1:]

            if len(loss_mask) == loss_mask.int().sum():
                # classification tasks
                shape = sample["net_input"]["src_tokens"].shape
                src_tokens = sample["net_input"]["src_tokens"].view(len(loss_mask)//option_num, option_num, -1)[:, 0]
                net_output, extra = model(
                    src_tokens
                )
                net_output = net_output.unsqueeze(1).expand(-1, option_num, -1, -1).reshape(len(loss_mask), len(src_tokens[0]), -1)
            else: 
                # multi-choice tasks
                net_output, extra = model(
                    sample["net_input"]["src_tokens"]
                )
                
            net_output = net_output[:, :-1, :]
            net_output = (net_output, extra)
            targets = sample["net_input"]["src_tokens"][:, 1:].unsqueeze(-1)

            lprobs = model.get_normalized_probs(net_output, log_probs=True)
            loss = torch.gather(lprobs, -1, targets).squeeze(-1) * (loss_mask != False).int()
            loss = loss.sum(-1)
            loss = loss / loss_mask.int().sum(-1)

            fewshot_labels = sample["targets"].view(-1)
            pred_label = torch.argmax(loss.view(-1, option_num), dim=1)
            target_label = fewshot_labels.view(-1, option_num)[:,0]

            logging_output = {}

            logging_output.update(
                {
                    "loss": loss.sum().data,
                    "ntokens": sample["ntokens"],
                    "nsentences": sample_size,
                    "sample_size": sample_size,
                    "ncorrect": (pred_label == target_label).sum(),
                    "npos": (target_label == 0).sum(),
                    "nneg": (target_label == 1).sum(),
                }
            )

            return loss, sample_size, logging_output
        else:
            # generation tasks
            src_tokens = sample["net_input"]["src_tokens"][0]
            generated_tokens_all = self.generate(sample, beam=self.cfg.beam)

            pred_str = self.decode(generated_tokens_all[0]["tokens"][len(src_tokens)-1:])

            assert self.task.answer_set is not None
            idx = sample['id']
            golden_str_set = self.task.answer_set[idx]
            
            max_f1 = 0
            max_ncorrect = 0
            for golden_str in golden_str_set:
                ncorrect = compute_exact(golden_str, pred_str)
                f1 = compute_f1(golden_str, pred_str)
                if f1 > max_f1:
                    max_f1 = f1
                if ncorrect > max_ncorrect:
                    max_ncorrect = ncorrect
                if max_ncorrect == 1:
                    break

            sample_size = len(sample["net_input"]["src_tokens"])
            logging_output = {
                "loss": 0,
                "ntokens": sample["ntokens"],
                "nsentences": sample_size,
                "sample_size": sample_size,
                "ncorrect": max_ncorrect,
                "f1": max_f1
            }
            
            return 0, sample_size, logging_output
Compute the loss for the given sample.

        Returns a tuple with three elements:
        1) the loss
        2) the sample size, which is used as the denominator for the gradient
        3) logging outputs to display while training
        
structured_prompting/fairseq-version/struprompting/criterions/fs_eval.py