x we have check tensor , or numpy apply api according to it 
return  only onto same device
NOTE : if any tuple  format of data is coming covert into tensor in the below api 
all changes in the api also
def apply_attack(
    attack_name: str,
    classifier: Any,
    x: np.ndarray,
    y: Optional[np.ndarray] = None,
    new_params: Optional[Dict[str, Any]] = None,
) -> np.ndarray:
    """
    Apply a single adversarial attack with the ability to update attack parameters.
    """
    logger.info(f"Initializing attack: {attack_name}")
    try:
        attack_class = get_attack_class(attack_name)
        if not attack_class:
            raise NotImplementedError(f"The attack '{attack_name}' is not supported.")

        params = get_attack_parameters(attack_name)

        if new_params:
            params = update_attack_parameters(params, new_params)

        # Correctly assign the classifier or estimator
        if "estimator" in params:
            params["estimator"] = classifier
        if "classifier" in params:
            params["classifier"] = classifier

        # Handle special parameters if necessary
        if attack_name == "AutoAttack":
            params["estimator_orig"] = classifier
        elif attack_name == "AdversarialPatchPyTorch":
            params["estimator"] = classifier

        # Initialize the attack with the classifier and parameters
        attack = attack_class(**params)
        
        # Ensure inputs are on CPU and in numpy format
        if isinstance(x, torch.Tensor):
            original_device = x.device  # Store the original device
            x = x.detach().cpu()
        if isinstance(y, torch.Tensor):
            y = y.detach().cpu()
            
        # Convert to numpy arrays
        x_numpy = x.numpy() if isinstance(x, torch.Tensor) else x
        y_numpy = y.numpy() if isinstance(y, torch.Tensor) else y
        
        # Ensure labels are in the correct format (integers)
        if y_numpy is not None:
            y_numpy = y_numpy.astype(np.int64)
            
        try:
            # Generate adversarial examples
            adversarial_examples = attack.generate(x=x_numpy, y=y_numpy)
            
            # Convert back to tensor if input was tensor
            if isinstance(x, torch.Tensor):
                adversarial_examples = torch.from_numpy(adversarial_examples).to(original_device)
                
            logger.info(f"Attack '{attack_name}' performed successfully.")
            return adversarial_examples
                
        except Exception as e:
            logger.error(f"Attack generation failed: {str(e)}")
            raise

    except ValueError as ve:
        logger.error(f"Value error: {ve}")
        raise
    except TypeError as te:
        logger.error(
            f"Type error while initializing '{attack_name}': {te}. Check parameter compatibility."
        )
        raise
    except NotImplementedError as nie:
        logger.warning(str(nie))
        raise
    except Exception as e:
        logger.error(f"Unexpected error during '{attack_name}': {e}")
        raise
note below fixed:
for attack in LIST_OF_ATTACKS:
        # Apply attack
        adversarial_images = apply_attack(
            attack_name=attack,
            classifier=classifier,
            x=images,
            y=labels
        )

        # Forward pass through the model
        model_output = wrapped_model(adversarial_images)
        
        # Calculate metrics
        metrics_pipeline = MetricsCalculator(preds=model_output, labels=labels, num_classes=1000)
        metrics = metrics_pipeline.get_all_metrics()
        
        # Optionally, log or store the metrics
        print(metrics)