def _poison__pytorch(self, x_poison: np.ndarray, y_poison: np.ndarray) -> tuple[Any, Any]:
        """
        Optimize the poison by matching the gradient within the perturbation budget.

        :param x_poison: List of samples to poison.
        :param y_poison: List of the labels for x_poison.
        :return: A pair of poisoned samples, B-score (cosine similarity of the gradients).
        """

        import torch

        device = "cuda" if torch.cuda.is_available() else "cpu"

        class PoisonDataset(torch.utils.data.Dataset):
            """
            Iterator for a dataset to poison.
            """

            def __init__(self, x: np.ndarray, y: np.ndarray):
                self.len = x.shape[0]
                self.x = torch.as_tensor(x, dtype=torch.float)
                self.y = torch.as_tensor(y)

            def __getitem__(self, index):
                return self.x[index], torch.as_tensor([index]), self.y[index]

            def __len__(self):
                return self.len

        trainloader = torch.utils.data.DataLoader(
            PoisonDataset(x_poison, y_poison), batch_size=self.batch_size, shuffle=False, num_workers=1
        )

        epoch_iterator = (
            trange(self.initial_epoch, self.max_epochs)
            if self.verbose > 0
            else range(self.initial_epoch, self.max_epochs)
        )
        for _ in epoch_iterator:
            batch_iterator = tqdm(trainloader) if isinstance(self.verbose, int) and self.verbose >= 2 else trainloader
            sum_loss = 0
            count = 0
            for x, indices, y in batch_iterator:
                x = x.to(device)
                y = y.to(device)
                indices = indices.to(device)
                self.backdoor_model.zero_grad()
                loss, poisoned_samples = self.backdoor_model(x, indices, y, self.grad_ws_norm)
                loss.backward()
                self.backdoor_model.noise_embedding.embedding_layer.weight.grad.sign_()
                self.optimizer.step()
                sum_loss += loss.clone().cpu().detach().numpy()
                count += 1
            if self.verbose > 0:
                epoch_iterator.set_postfix(loss=sum_loss / count)
            self.lr_schedule.step()

        B_sum = 0  # pylint: disable=invalid-name
        count = 0
        all_poisoned_samples = []
        self.backdoor_model.eval()
        poisonloader = torch.utils.data.DataLoader(
            PoisonDataset(x_poison, y_poison), batch_size=self.batch_size, shuffle=False, num_workers=1
        )
        for x, indices, y in poisonloader:
            x = x.to(device)
            y = y.to(device)
            indices = indices.to(device)
            B, poisoned_samples = self.backdoor_model(x, indices, y, self.grad_ws_norm)  # pylint: disable=invalid-name
            all_poisoned_samples.append(poisoned_samples.detach().cpu().numpy())
            B_sum += B.detach().cpu().numpy()  # pylint: disable=invalid-name
            count += 1
        return np.concatenate(all_poisoned_samples, axis=0), B_sum / count

        Optimize the poison by matching the gradient within the perturbation budget.

        :param x_poison: List of samples to poison.
        :param y_poison: List of the labels for x_poison.
        :return: A pair of poisoned samples, B-score (cosine similarity of the gradients).
        
art/attacks/poisoning/gradient_matching_attack.py