code: def kernel_mmd(x, y, n_perm=1000, kernel=GaussianKernel()):
      """Implements the kernel MMD two-sample test.
  
      It is modelled after the kernel MMD paper and code:
      A. Gretton et al.: A kernel two-sample test, JMLR 13 (2012)
      http://www.gatsby.ucl.ac.uk/~gretton/mmd/mmd.htm
  
      The arguments `x` and `y` should be two-dimensional tensors.
      The first is the batch dimension (which may differ), the second
      the features (which must be the same on both `x` and `y`).
  
      `n_perm` is number of bootstrap permutations to get p-value, pass `None` to not get p-value.
      """
  
      n, d = x.shape
      m, d2 = y.shape
      torchdrift.utils.check(d == d2, "feature dimension mismatch")
      xy = torch.cat([x.detach(), y.detach()], dim=0)
      dists = torch.cdist(xy, xy, p=2.0)
      # we are a bit sloppy here as we just keep the diagonal and everything twice
      k = kernel(dists)
      k_x = k[:n, :n]
      k_y = k[n:, n:]
      k_xy = k[:n, n:]
      # The diagonals are always 1 (up to numerical error, this is (3) in Gretton et al.)
      # note that their code uses the biased (and differently scaled mmd)
      mmd = (
          k_x.sum() / (n * (n - 1))
          + k_y.sum() / (m * (m - 1))
          - 2 * k_xy.sum() / (n * m)
      )
      if n_perm is None:
          return mmd
      mmd_0s = []
      count = 0
      for i in range(n_perm):
          # this isn't efficient, it would be lovely to generate a cuda kernel or C++ for loop and do the
          # permutation on the fly...
          pi = torch.randperm(n + m, device=x.device)
          k = k[pi][:, pi]
          k_x = k[:n, :n]
          k_y = k[n:, n:]
          k_xy = k[:n, n:]
          # The diagonals are always 1 (up to numerical error, this is (3) in Gretton et al.)
          mmd_0 = (
              k_x.sum() / (n * (n - 1))
              + k_y.sum() / (m * (m - 1))
              - 2 * k_xy.sum() / (n * m)
          )
          mmd_0s.append(mmd_0)
          count = count + (mmd_0 > mmd)
      # pyplot.hist(torch.stack(mmd_0s, dim=0).tolist(), bins=50)
      # true_divide: torch 1.6 compat replace with "/" after October 2021
      p_val = torch.true_divide(count, n_perm)
  
      return mmd, p_val
  
imports: from typing import Optional
  import torch
  from .None import Detector
  import torchdrift.utils
label_desc: Use image node_id 1 for calling a global function with example usage: kernel_mmd(x, y, n_perm, kernel) and returns: mmd,  p_val, mmd