code: def _nd_convolution(
      self,
      n,
      input_channels_per_group,
      output_channels_per_group,
      batch_size,
      stride,
      size,
      kernel,
      dilation,
      pad,
      group,
      order,
      use_bias,
      engine,
      force_algo_fwd,
      force_algo_dgrad,
      force_algo_wgrad,
      gc,
      dc,
  ):
      # TODO: Group conv in NHWC not implemented for GPU yet.
      # TODO: Group 1D conv in NCHW not implemented for GPU yet.
      assume(
          group == 1
          or (n != 1 and order == "NCHW")
          or gc.device_type == caffe2_pb2.CPU
      )
      if group != 1 and (n == 1 or order == "NHWC"):
          dc = [d for d in dc if d.device_type == caffe2_pb2.CPU]
  
      input_channels = group * input_channels_per_group
      output_channels = group * output_channels_per_group
  
      dkernel = dilation * (kernel - 1) + 1
      for op_type in ["Conv", "Conv" + str(n) + "D"]:
          op = core.CreateOperator(
              op_type,
              ["X", "w", "b"] if use_bias else ["X", "w"],
              ["Y"],
              strides=[stride] * n,
              kernels=[kernel] * n,
              dilations=[dilation] * n,
              pads=[pad] * n * 2,
              group=group,
              order=order,
              engine=engine,
              force_algo_fwd=force_algo_fwd,
              force_algo_dgrad=force_algo_dgrad,
              force_algo_wgrad=force_algo_wgrad,
          )
  
          input_dims = [batch_size, input_channels]
          input_dims.extend([size] * n)
          filter_dims = [output_channels, input_channels // group]
          filter_dims.extend([kernel] * n)
  
          X = np.random.rand(*input_dims).astype(np.float32) - 0.5
          w = np.random.rand(*filter_dims).astype(np.float32) - 0.5
          b = np.random.rand(output_channels).astype(np.float32) - 0.5
          if order == "NHWC":
              X = utils.NCHW2NHWC(X)
              w = utils.NCHW2NHWC(w)
  
          inputs = [X, w, b] if use_bias else [X, w]
  
          if size + pad + pad < dkernel or size + pad + pad < dkernel:
              with self.assertRaises(RuntimeError):
                  self.assertDeviceChecks(dc, op, inputs, [0])
              return
  
          self.assertDeviceChecks(dc, op, inputs, [0])
          for i in range(len(inputs)):
              self.assertGradientChecks(gc, op, inputs, i, [0])
  
imports: import collections
  import functools
  import unittest
  import caffe2.python._import_c_extension
  import caffe2.python.hip_test_util
  import caffe2.python.hypothesis_test_util
  import caffe2.python.serialized_test.serialized_test_util
  import hypothesis.strategies
  import numpy
  from caffe2.proto import caffe2_pb2
  from caffe2.python import brew, core, utils, workspace
  from caffe2.python.model_helper import ModelHelper
  from hypothesis import assume, given, settings
label_desc: Use image node_id 4 for calling the TestConvolution obj's underlying member method code with example usage: obj._nd_convolution(n, input_channels_per_group, output_channels_per_group, batch_size, stride, size, kernel, dilation, pad, group, order, use_bias, engine, force_algo_fwd, force_algo_dgrad, force_algo_wgrad, gc, dc) without return types