def forward(self, images: torch.Tensor) -> tuple[list[torch.Tensor], list[torch.Tensor]] | InferenceBatch:
        """Forward pass through the model.

        The behavior differs between training and evaluation modes:
        - Training: Returns encoder and decoder features for computing loss
        - Evaluation: Returns anomaly maps and scores

        Args:
            images (torch.Tensor): Input tensor of shape ``(N, C, H, W)`` where
                ``N`` is batch size, ``C`` is number of channels, ``H`` and ``W``
                are height and width.

        Returns:
            tuple[list[torch.Tensor], list[torch.Tensor]] | InferenceBatch:
                - In training mode: Tuple of lists containing encoder and decoder
                  features
                - In evaluation mode: ``InferenceBatch`` containing anomaly maps
                  and scores

        Example:
            >>> import torch
            >>> model = ReverseDistillationModel(
            ...     backbone="wide_resnet50_2",
            ...     input_size=(256, 256),
            ...     layers=["layer1", "layer2", "layer3"],
            ...     anomaly_map_mode="multiply"
            ... )
            >>> input_tensor = torch.randn(1, 3, 256, 256)
            >>> # Training mode
            >>> model.train()
            >>> encoder_features, decoder_features = model(input_tensor)
            >>> # Evaluation mode
            >>> model.eval()
            >>> predictions = model(input_tensor)
        """
        self.encoder.eval()

        if self.tiler:
            images = self.tiler.tile(images)
        encoder_features = self.encoder(images)
        encoder_features = list(encoder_features.values())
        decoder_features = self.decoder(self.bottleneck(encoder_features))

        if self.tiler:
            for i, features in enumerate(encoder_features):
                encoder_features[i] = self.tiler.untile(features)
            for i, features in enumerate(decoder_features):
                decoder_features[i] = self.tiler.untile(features)

        if self.training:
            return encoder_features, decoder_features

        anomaly_map = self.anomaly_map_generator(encoder_features, decoder_features)
        pred_score = torch.amax(anomaly_map, dim=(-2, -1))
        return InferenceBatch(pred_score=pred_score, anomaly_map=anomaly_map)
Forward pass through the model.

        The behavior differs between training and evaluation modes:
        - Training: Returns encoder and decoder features for computing loss
        - Evaluation: Returns anomaly maps and scores

        Args:
            images (torch.Tensor): Input tensor of shape ``(N, C, H, W)`` where
                ``N`` is batch size, ``C`` is number of channels, ``H`` and ``W``
                are height and width.

        Returns:
            tuple[list[torch.Tensor], list[torch.Tensor]] | InferenceBatch:
                - In training mode: Tuple of lists containing encoder and decoder
                  features
                - In evaluation mode: ``InferenceBatch`` containing anomaly maps
                  and scores

        Example:
            >>> import torch
            >>> model = ReverseDistillationModel(
            ...     backbone="wide_resnet50_2",
            ...     input_size=(256, 256),
            ...     layers=["layer1", "layer2", "layer3"],
            ...     anomaly_map_mode="multiply"
            ... )
            >>> input_tensor = torch.randn(1, 3, 256, 256)
            >>> # Training mode
            >>> model.train()
            >>> encoder_features, decoder_features = model(input_tensor)
            >>> # Evaluation mode
            >>> model.eval()
            >>> predictions = model(input_tensor)
        
src/anomalib/models/image/reverse_distillation/torch_model.py