def cosine_similarity(input1: torch.Tensor, input2: torch.Tensor) -> torch.Tensor:
    """Compute pairwise cosine similarity matrix between two tensors.

    Computes the cosine similarity between all pairs of vectors in the two input tensors.
    The inputs can be either 2D or 3D tensors. For 2D inputs, an implicit batch
    dimension of 1 is added.

    Args:
        input1 (torch.Tensor): First input tensor of shape ``(N, D)`` or ``(B, N, D)``,
            where:
            - ``B`` is the optional batch dimension
            - ``N`` is the number of vectors in first input
            - ``D`` is the dimension of each vector
        input2 (torch.Tensor): Second input tensor of shape ``(M, D)`` or ``(B, M, D)``,
            where:
            - ``B`` is the optional batch dimension
            - ``M`` is the number of vectors in second input
            - ``D`` is the dimension of each vector (must match input1)

    Returns:
        torch.Tensor: Cosine similarity matrix of shape ``(N, M)`` for 2D inputs or
            ``(B, N, M)`` for 3D inputs, where each element ``[i,j]`` is the cosine
            similarity between vector ``i`` from ``input1`` and vector ``j`` from
            ``input2``.

    Examples:
        2D inputs (single batch):

        >>> input1 = torch.tensor([[1.0, 0.0, 0.0], [0.0, 1.0, 0.0]])
        >>> input2 = torch.tensor([[0.0, 1.0, 0.0], [1.0, 1.0, 0.0]])
        >>> cosine_similarity(input1, input2)
        tensor([[[0.0000, 0.7071],
                 [1.0000, 0.7071]]])

        Different sized inputs:

        >>> input1 = torch.randn(100, 128)  # 100 vectors of dimension 128
        >>> input2 = torch.randn(200, 128)  # 200 vectors of dimension 128
        >>> similarity = cosine_similarity(input1, input2)
        >>> similarity.shape
        torch.Size([100, 200])

        3D inputs (batched):

        >>> input1 = torch.randn(10, 100, 128)  # 10 batches of 100 vectors
        >>> input2 = torch.randn(10, 200, 128)  # 10 batches of 200 vectors
        >>> similarity = cosine_similarity(input1, input2)
        >>> similarity.shape
        torch.Size([10, 100, 200])

    Note:
        The function automatically handles both 2D and 3D inputs by adding a batch
        dimension to 2D inputs. The vector dimension ``D`` must match between inputs.
    """
    ndim = input1.ndim
    input1 = input1.unsqueeze(0) if input1.ndim == 2 else input1
    input2 = input2.repeat(input1.shape[0], 1, 1) if input2.ndim == 2 else input2

    input1_norm = nn.functional.normalize(input1, p=2, dim=-1)
    input2_norm = nn.functional.normalize(input2, p=2, dim=-1)
    similarity = torch.bmm(input1_norm, input2_norm.transpose(-2, -1))
    if ndim == 2:
        return similarity.squeeze(0)
    return similarity
Compute pairwise cosine similarity matrix between two tensors.

    Computes the cosine similarity between all pairs of vectors in the two input tensors.
    The inputs can be either 2D or 3D tensors. For 2D inputs, an implicit batch
    dimension of 1 is added.

    Args:
        input1 (torch.Tensor): First input tensor of shape ``(N, D)`` or ``(B, N, D)``,
            where:
            - ``B`` is the optional batch dimension
            - ``N`` is the number of vectors in first input
            - ``D`` is the dimension of each vector
        input2 (torch.Tensor): Second input tensor of shape ``(M, D)`` or ``(B, M, D)``,
            where:
            - ``B`` is the optional batch dimension
            - ``M`` is the number of vectors in second input
            - ``D`` is the dimension of each vector (must match input1)

    Returns:
        torch.Tensor: Cosine similarity matrix of shape ``(N, M)`` for 2D inputs or
            ``(B, N, M)`` for 3D inputs, where each element ``[i,j]`` is the cosine
            similarity between vector ``i`` from ``input1`` and vector ``j`` from
            ``input2``.

    Examples:
        2D inputs (single batch):

        >>> input1 = torch.tensor([[1.0, 0.0, 0.0], [0.0, 1.0, 0.0]])
        >>> input2 = torch.tensor([[0.0, 1.0, 0.0], [1.0, 1.0, 0.0]])
        >>> cosine_similarity(input1, input2)
        tensor([[[0.0000, 0.7071],
                 [1.0000, 0.7071]]])

        Different sized inputs:

        >>> input1 = torch.randn(100, 128)  # 100 vectors of dimension 128
        >>> input2 = torch.randn(200, 128)  # 200 vectors of dimension 128
        >>> similarity = cosine_similarity(input1, input2)
        >>> similarity.shape
        torch.Size([100, 200])

        3D inputs (batched):

        >>> input1 = torch.randn(10, 100, 128)  # 10 batches of 100 vectors
        >>> input2 = torch.randn(10, 200, 128)  # 10 batches of 200 vectors
        >>> similarity = cosine_similarity(input1, input2)
        >>> similarity.shape
        torch.Size([10, 100, 200])

    Note:
        The function automatically handles both 2D and 3D inputs by adding a batch
        dimension to 2D inputs. The vector dimension ``D`` must match between inputs.
    
src/anomalib/models/image/winclip/utils.py