Undefined symbol: cpublas4gemm... when loading extension

71M073J: <p>Hi, when building a pytorch extension for a project, the building succeeds, but when loading it, the import fails as shown below.</p>
<pre data-code-wrap="import"><code class="lang-import">import torch.nn as nn
import torch.functional as F
from torch.utils.cpp_extension import load

perf = load(name="test1", sources=["ConvolutionMM2d_.cpp"], verbose=True)
/.../
</code></pre>
<p>I get the error:</p>
<pre><code class="lang-auto">ImportError: /home/username/.cache/torch_extensions/py310_cu121/test1/test1.so: undefined symbol: _ZN2at6native7cpublas4gemmENS0_13TransposeTypeES2_lllfPKN3c104HalfElS6_lfPS4_l
</code></pre>
<p>here is the cpp file of <a href="https://github.com/pytorch/pytorch/blob/v2.3.0/aten/src/ATen/native/ConvolutionMM2d.cpp" rel="noopener nofollow ugc">ConvolutionMM2d_.cpp</a>, from v2.3.0 tag of pytorch source, edited such that it can be registered as an extension. (my project involves small changes in convolution algorithm - so this would be a great starting point)</p>
<pre><code class="lang-auto">#include &lt;torch/extension.h&gt;

#include &lt;vector&gt;
#include &lt;iostream&gt;

#define TORCH_ASSERT_ONLY_METHOD_OPERATORS

#include &lt;ATen/core/Tensor.h&gt;
#include &lt;ATen/Dispatch.h&gt;
#include &lt;ATen/Parallel.h&gt;
#include &lt;ATen/TensorUtils.h&gt;
#include &lt;ATen/div_rtn.h&gt;
#include &lt;ATen/native/ConvUtils.h&gt;
#include &lt;ATen/native/CPUBlas.h&gt;
#include &lt;ATen/native/Unfold2d.h&gt;
#include &lt;c10/util/irange.h&gt;

#ifndef AT_PER_OPERATOR_HEADERS
#include &lt;ATen/Functions.h&gt;
#include &lt;ATen/NativeFunctions.h&gt;
#else
#include &lt;ATen/ops/_slow_conv2d_backward_native.h&gt;
#include &lt;ATen/ops/_slow_conv2d_forward.h&gt;
#include &lt;ATen/ops/_slow_conv2d_forward_native.h&gt;
#include &lt;ATen/ops/empty.h&gt;
#include &lt;ATen/ops/sum.h&gt;
#include &lt;ATen/ops/thnn_conv2d_native.h&gt;
#endif

namespace at::native {

namespace {

static Tensor compute_columns2d(
    const Tensor&amp; input,
    IntArrayRef padding,
    IntArrayRef stride,
    IntArrayRef kernel_size,
    bool is_channels_last) {
  const int64_t kernel_height = kernel_size[0];
  const int64_t kernel_width = kernel_size[1];
  const int64_t pad_height = padding[0];
  const int64_t pad_width = padding[1];
  const int64_t stride_height = stride[0];
  const int64_t stride_width = stride[1];
  const int64_t batch_size = input.size(0);
  const int64_t n_input_plane = input.size(1);
  const int64_t input_height = input.size(2);
  const int64_t input_width = input.size(3);
  const int64_t output_height = (input_height + 2 * pad_height - kernel_height) / stride_height + 1;
  const int64_t output_width =  (input_width + 2 * pad_width - kernel_width) / stride_width + 1;

  Tensor columns;
  if ((kernel_height == 1) &amp;&amp; (stride_height == 1) &amp;&amp; (pad_height == 0) &amp;&amp;
      (kernel_width == 1) &amp;&amp; (stride_width == 1) &amp;&amp; (pad_width == 0)) {
    // Columns are just a view on the input for the 1x1 kernel special case.
    if (is_channels_last) {
      columns = input.as_strided({batch_size, output_height * output_width, n_input_plane},
          {output_height * output_width * n_input_plane, n_input_plane, 1}).detach();
    } else {
      columns = input.view({batch_size, n_input_plane, output_height * output_width}).detach();
    }
  } else {
    int64_t row = is_channels_last ?
        output_height * output_width : n_input_plane * kernel_height * kernel_width;
    int64_t col = is_channels_last ?
        kernel_height * kernel_width * n_input_plane : output_height * output_width;
    columns = at::empty({batch_size, row, col}, input.options());
    AT_DISPATCH_ALL_TYPES_AND2(kBFloat16, kHalf, input.scalar_type(), "slow_conv2d_cpu", [&amp;]{
      auto input_a = input.accessor&lt;scalar_t, 4&gt;();
      auto columns_a = columns.accessor&lt;scalar_t, 3&gt;();

      at::parallel_for(0, batch_size, 0, [&amp;](int64_t start, int64_t end) {
        for (const auto t : c10::irange(start, end)) {
          auto input_t = input_a[t];
          auto columns_t = columns_a[t];
          unfolded2d_copy_stub(
              kCPU,
              c10::CppTypeToScalarType&lt;scalar_t&gt;::value,
              columns_t.data(),
              input_t.data(),
              kernel_height,
              kernel_width,
              stride_height,
              stride_width,
              pad_height,
              pad_width,
              n_input_plane,
              input_height,
              input_width,
              output_height,
              output_width,
              is_channels_last);
        }
      });
    });
  }

  return columns.contiguous();
}

static inline void slow_conv2d_shape_check(
    const Tensor&amp; input,
    const Tensor&amp; grad_output,
    const Tensor&amp; weight,
    const Tensor&amp; bias,
    int64_t kernel_height,
    int64_t kernel_width,
    int64_t stride_height,
    int64_t stride_width,
    int64_t pad_height,
    int64_t pad_width,
    bool weight_optional) {
  TORCH_CHECK(
      kernel_width &gt; 0 &amp;&amp; kernel_height &gt; 0,
      "kernel size should be greater than zero, but got kernel_height: ",
      kernel_height,
      " kernel_width: ",
      kernel_width);
  TORCH_CHECK(
      stride_width &gt; 0 &amp;&amp; stride_height &gt; 0,
      "stride should be greater than zero, but got stride_height: ",
      stride_height,
      " stride_width: ",
      stride_width);

  if (weight.defined()) {
    TORCH_CHECK(
        weight.numel() &gt; 0 &amp;&amp; (weight.dim() == 2 || weight.dim() == 4),
        "non-empty 2D or 4D weight tensor expected, but got: ",
        weight.sizes());
    if (bias.defined()) {
      check_dim_size(bias, 1, 0, weight.size(0));
    }
  } else {
    TORCH_CHECK(weight_optional, "weight tensor is undefined");
  }

  const int64_t ndim = input.dim();
  const int64_t dim_planes = 1;
  const int64_t dim_height = 2;
  const int64_t dim_width = 3;

  // Allow for empty batch size and channel size but not other dimensions
  TORCH_CHECK(ndim == 4, "Expected 4D input tensor, but got: ", input.sizes());
  for (const auto dim : c10::irange(2, ndim)) {
    TORCH_CHECK(input.size(dim) != 0,
                "Expected non-zero size for input dimension ", dim,
                ", but got input shape: ", input.sizes(), ". Only the batch and channel dimensions support size 0.");
  }

  const int64_t input_height = input.size(dim_height);
  const int64_t input_width = input.size(dim_width);

  const int64_t exact_input_height = input_height + 2 * pad_height;
  const int64_t exact_input_width = input_width + 2 * pad_width;

  TORCH_CHECK(
      exact_input_height &gt;= kernel_height &amp;&amp; exact_input_width &gt;= kernel_width,
      "Calculated padded input size per channel: (",
      exact_input_height,
      " x ",
      exact_input_width,
      "). ",
      "Kernel size: (",
      kernel_height,
      " x ",
      kernel_width,
      "). Kernel size can't be greater than actual input size");

  const int64_t output_height =
      div_rtn&lt;int64_t&gt;(exact_input_height - kernel_height, stride_height) + 1;
  const int64_t output_width =
      div_rtn&lt;int64_t&gt;(exact_input_width - kernel_width, stride_width) + 1;

  TORCH_CHECK(
      output_width &gt;= 1 &amp;&amp; output_height &gt;= 1,
      "Given input size per channel: (",
      input_height,
      " x ",
      input_width,
      "). "
      "Calculated output size per channel: (",
      output_height,
      " x ",
      output_width,
      "). Output size is too small");

  if (weight.defined()) {
    int64_t n_input_plane = weight.size(1);
    if (weight.dim() == 2) {
      n_input_plane /= (kernel_height * kernel_width);
    }
    if (input.size(1) != 0) {
      check_dim_size(input, ndim, dim_planes, n_input_plane);
    }
  }

  if (grad_output.defined()) {
    if (weight.defined()) {
      int64_t n_output_plane = weight.size(0);
      check_dim_size(grad_output, ndim, dim_planes, n_output_plane);
    } else if (bias.defined()) {
      TORCH_CHECK(bias.numel() &gt; 0, "non-empty bias tensor expected");
      const int64_t n_output_plane = bias.dim() == 0 ? 1 : bias.size(0);
      check_dim_size(grad_output, ndim, dim_planes, n_output_plane);
    }
    check_dim_size(grad_output, ndim, dim_height, output_height);
    check_dim_size(grad_output, ndim, dim_width, output_width);
  }
}

static inline Tensor view_weight_2d(const Tensor&amp; weight_,
    at::MemoryFormat memory_format = at::MemoryFormat::Contiguous) {
  Tensor weight = weight_.contiguous(memory_format);
  if (weight.dim() == 4) {
    const int64_t s1 = weight.size(0);
    const int64_t s2 = weight.size(1) * weight.size(2) * weight.size(3);
    return memory_format == at::MemoryFormat::ChannelsLast
        ? weight.as_strided({s1, s2}, {s2, 1}) // CL: view as {oc, kh*kw*ic}
        : weight.view({s1, s2}); // CF: view as {oc, ic*kh*kw}
  } else {
    return weight;
  }
}

template &lt;typename scalar_t&gt;
static void slow_conv2d_update_output_frame(
    TensorAccessor&lt;scalar_t, 3&gt; input,
    TensorAccessor&lt;scalar_t, 3&gt; output,
    TensorAccessor&lt;scalar_t, 2&gt; weight,
    bool has_bias,
    TensorAccessor&lt;scalar_t, 2&gt; finput,
    int64_t kernel_height,
    int64_t kernel_width,
    int64_t stride_height,
    int64_t stride_width,
    int64_t pad_height,
    int64_t pad_width,
    int64_t n_input_plane,
    int64_t input_height,
    int64_t input_width,
    int64_t n_output_plane,
    int64_t output_height,
    int64_t output_width,
    bool is_channels_last) {
  const int beta = has_bias ? 1 : 0;

  // Compute out = weight * input
  // Note gemm expects fortran order, so all 3 matrices are transposed.
  // Swapping argument order cancels this, since C == AB &lt;=&gt; T(C) == T(B)T(A)
  if (is_channels_last) {
    const int64_t m = n_output_plane;
    const int64_t n = output_height * output_width;
    const int64_t k = n_input_plane * kernel_height * kernel_width;

    const int64_t lda = k;
    const int64_t ldb = k;
    const int64_t ldc = m;

    at::native::cpublas::gemm(
        TransposeType::Transpose,
        TransposeType::NoTranspose,
        m, n, k,
        static_cast&lt;scalar_t&gt;(1),
        weight.data(), lda,
        finput.data(), ldb,
        static_cast&lt;scalar_t&gt;(beta),
        output.data(), ldc);
  } else {
    const int64_t m = output_height * output_width;
    const int64_t n = n_output_plane;
    const int64_t k = n_input_plane * kernel_height * kernel_width;

    const int64_t lda = m;
    const int64_t ldb = k;
    const int64_t ldc = m;

    at::native::cpublas::gemm(
        TransposeType::NoTranspose,
        TransposeType::NoTranspose,
        m, n, k,
        static_cast&lt;scalar_t&gt;(1),
        finput.data(), lda,
        weight.data(), ldb,
        static_cast&lt;scalar_t&gt;(beta),
        output.data(), ldc);
  }
}

template &lt;typename scalar_t&gt;
void slow_conv2d_backward_update_grad_input_frame(
    TensorAccessor&lt;scalar_t, 3&gt; grad_input,
    TensorAccessor&lt;scalar_t, 3&gt; grad_output,
    TensorAccessor&lt;scalar_t, 2&gt; weight,
    scalar_t *fgrad_input,
    int64_t kernel_height,
    int64_t kernel_width,
    int64_t stride_height,
    int64_t stride_width,
    int64_t pad_height,
    int64_t pad_width,
    bool is_channels_last) {
  // Compute fgrad_input = weight.T * grad_output.reshape({grad_output.shape(0), -1})
  // Note gemm expects fortran order, so all 3 matrices are transposed.
  // Swapping argument order cancels this, since C == AB &lt;=&gt; T(C) == T(B)T(A)
  if (is_channels_last) {
    const int64_t m = weight.size(1);
    const int64_t n = grad_output.size(1) * grad_output.size(2);
    const int64_t k = weight.size(0);

    const int64_t lda = m;
    const int64_t ldb = k;
    const int64_t ldc = m;

    at::native::cpublas::gemm(
        TransposeType::NoTranspose,
        TransposeType::NoTranspose,
        m, n, k,
        static_cast&lt;scalar_t&gt;(1),
        weight.data(), lda,
        grad_output.data(), ldb,
        static_cast&lt;scalar_t&gt;(0),
        fgrad_input, ldc);
  } else {
    const int64_t m = grad_output.size(1) * grad_output.size(2);
    const int64_t n = weight.size(1);
    const int64_t k = weight.size(0);

    const int64_t lda = m;
    const int64_t ldb = n;
    const int64_t ldc = m;

    at::native::cpublas::gemm(
        TransposeType::NoTranspose,
        TransposeType::Transpose,
        m, n, k,
        static_cast&lt;scalar_t&gt;(1),
        grad_output.data(), lda,
        weight.data(), ldb,
        static_cast&lt;scalar_t&gt;(0),
        fgrad_input, ldc);
  }

  unfolded2d_acc_stub(
      kCPU,
      c10::CppTypeToScalarType&lt;scalar_t&gt;::value,
      fgrad_input,
      grad_input.data(),
      kernel_height,
      kernel_width,
      stride_height,
      stride_width,
      pad_height,
      pad_width,
      grad_input.size(0),
      grad_input.size(1),
      grad_input.size(2),
      grad_output.size(1),
      grad_output.size(2),
      is_channels_last);
}

void slow_conv2d_backward_out_cpu_template(
    Tensor&amp; grad_input,
    const Tensor&amp; grad_output_,
    const Tensor&amp; input_,
    const Tensor&amp; weight_,
    IntArrayRef kernel_size,
    IntArrayRef stride,
    IntArrayRef padding) {
  const int64_t kernel_height = kernel_size[0];
  const int64_t kernel_width = kernel_size[1];
  const int64_t pad_height = padding[0];
  const int64_t pad_width = padding[1];
  const int64_t stride_height = stride[0];
  const int64_t stride_width = stride[1];

  bool use_channels_last = thnn_conv_use_channels_last(input_, weight_);
  auto memory_format = use_channels_last ? at::MemoryFormat::ChannelsLast : at::MemoryFormat::Contiguous;

  const Tensor weight = view_weight_2d(weight_, memory_format);
  slow_conv2d_shape_check(
      input_,
      grad_output_,
      weight,
      Tensor(),
      kernel_height,
      kernel_width,
      stride_height,
      stride_width,
      pad_height,
      pad_width,
      false);

  const Tensor input = input_.contiguous(memory_format);

  // Compute shape of columnized data excluding batch dim.
  const int64_t batch_size = input.size(0);
  const int64_t n_input_plane = input.size(1);
  const int64_t input_height = input.size(2);
  const int64_t input_width = input.size(3);
  const int64_t output_height = (input_height + 2 * pad_height - kernel_height) / stride_height + 1;
  const int64_t output_width = (input_width + 2 * pad_width - kernel_width) / stride_width + 1;
  const int64_t fgrad_input_size = n_input_plane * kernel_height * kernel_width * output_height * output_width;

  const Tensor grad_output = grad_output_.contiguous(memory_format);
  grad_input.resize_as_(input, memory_format);
  grad_input.zero_();
  TORCH_CHECK(grad_input.is_contiguous(memory_format), "slow_conv2d: grad_input must be contiguous");

  AT_DISPATCH_FLOATING_TYPES_AND2(
      kBFloat16, kHalf, input.scalar_type(), "slow_conv2d_cpu_grad_input", [&amp;] {
    auto grad_output_a = grad_output.accessor&lt;scalar_t, 4&gt;();
    auto grad_input_a = grad_input.accessor&lt;scalar_t, 4&gt;();
    auto weight_a = weight.accessor&lt;scalar_t, 2&gt;();

    at::parallel_for(0, batch_size, 0, [&amp;](int64_t start, int64_t end) {
      auto fgrad_input = std::make_unique&lt;scalar_t[]&gt;(fgrad_input_size);
      for (const auto t : c10::irange(start, end)) {
        auto grad_input_t = grad_input_a[t];
        auto grad_output_t = grad_output_a[t];
        slow_conv2d_backward_update_grad_input_frame(
            grad_input_t,
            grad_output_t,
            weight_a,
            fgrad_input.get(),
            kernel_height,
            kernel_width,
            stride_height,
            stride_width,
            pad_height,
            pad_width,
            use_channels_last);
      }
    });
  });
}

template &lt;typename scalar_t&gt;
void slow_conv2d_backward_weight_frame(
    TensorAccessor&lt;scalar_t, 2&gt; grad_weight,
    TensorAccessor&lt;scalar_t, 3&gt; grad_output,
    TensorAccessor&lt;scalar_t, 2&gt; finput,
    bool is_channels_last) {
  // Compute grad_weight += grad_output.reshape({grad_output.shape(0), -1}) * finput.T
  // Note gemm expects fortran order, so all 3 matrices are transposed.
  // Swapping argument order cancels this, since C == AB &lt;=&gt; T(C) == T(B)T(A)
  if (is_channels_last) {
    const int64_t m = finput.size(1);
    const int64_t n = grad_output.size(0);
    const int64_t k = grad_output.size(1) * grad_output.size(2);

    const int64_t lda = m;
    const int64_t ldb = n;
    const int64_t ldc = m;

    at::native::cpublas::gemm(
        TransposeType::NoTranspose,
        TransposeType::Transpose,
        m, n, k,
        static_cast&lt;scalar_t&gt;(1),
        finput.data(), lda,
        grad_output.data(), ldb,
        static_cast&lt;scalar_t&gt;(1),
        grad_weight.data(), ldc);
  } else {
    const int64_t m = finput.size(0);
    const int64_t n = grad_output.size(0);
    const int64_t k = grad_output.size(1) * grad_output.size(2);

    const int64_t lda = k;
    const int64_t ldb = k;
    const int64_t ldc = m;

    at::native::cpublas::gemm(
        TransposeType::Transpose,
        TransposeType::NoTranspose,
        m, n, k,
        static_cast&lt;scalar_t&gt;(1),
        finput.data(), lda,
        grad_output.data(), ldb,
        static_cast&lt;scalar_t&gt;(1),
        grad_weight.data(), ldc);
  }
}

static void slow_conv2d_backward_weight_out_cpu_template(
    Tensor&amp; grad_weight,
    const Tensor&amp; input,
    const Tensor&amp; grad_output_,
    IntArrayRef kernel_size,
    IntArrayRef stride,
    IntArrayRef padding) {
  const int64_t kernel_height = kernel_size[0];
  const int64_t kernel_width = kernel_size[1];
  const int64_t pad_height = padding[0];
  const int64_t pad_width = padding[1];
  const int64_t stride_height = stride[0];
  const int64_t stride_width = stride[1];

  bool use_channels_last = thnn_conv_use_channels_last(input, grad_weight);
  auto memory_format = use_channels_last ? at::MemoryFormat::ChannelsLast : at::MemoryFormat::Contiguous;

  TORCH_CHECK(grad_weight.is_contiguous(memory_format), "slow_conv2d: grad_weight must be contiguous");
  Tensor grad_weight_2d = view_weight_2d(grad_weight, memory_format);

  slow_conv2d_shape_check(
      input,
      grad_output_,
      grad_weight_2d,
      {},
      kernel_height,
      kernel_width,
      stride_height,
      stride_width,
      pad_height,
      pad_width,
      true);

  auto grad_output = grad_output_.contiguous(memory_format);
  Tensor finput = compute_columns2d(input, padding, stride, kernel_size, use_channels_last);

  const int64_t batch_size = input.size(0);

  AT_DISPATCH_FLOATING_TYPES_AND2(
      kBFloat16, kHalf, input.scalar_type(), "slow_conv2d_cpu_grad_weight", [&amp;] {
    auto grad_output_a = grad_output.accessor&lt;scalar_t, 4&gt;();
    auto grad_weight_2d_a = grad_weight_2d.accessor&lt;scalar_t, 2&gt;();
    auto finput_a = finput.accessor&lt;scalar_t, 3&gt;();

    for (const auto t : c10::irange(batch_size)) {
      auto grad_output_t = grad_output_a[t];
      auto finput_t = finput_a[t];

      slow_conv2d_backward_weight_frame(
          grad_weight_2d_a, grad_output_t, finput_t, use_channels_last);
    }
  });
}

} // namespace

Tensor&amp; slow_conv2d_forward_out_cpu(
    const Tensor&amp; self,
    const Tensor&amp; weight_,
    IntArrayRef kernel_size, const c10::optional&lt;Tensor&gt;&amp; bias_opt,
    IntArrayRef stride,
    IntArrayRef padding,
    Tensor&amp; output) {
  // See [Note: hacky wrapper removal for optional tensor]
  c10::MaybeOwned&lt;Tensor&gt; bias_maybe_owned = at::borrow_from_optional_tensor(bias_opt);
  const Tensor&amp; bias = *bias_maybe_owned;

  const int64_t kernel_height = kernel_size[0];
  const int64_t kernel_width = kernel_size[1];
  const int64_t pad_height = padding[0];
  const int64_t pad_width = padding[1];
  const int64_t stride_height = stride[0];
  const int64_t stride_width = stride[1];

  bool use_channels_last = thnn_conv_use_channels_last(self, weight_);
  auto memory_format = use_channels_last ? at::MemoryFormat::ChannelsLast : at::MemoryFormat::Contiguous;

  const Tensor weight_2d = view_weight_2d(weight_, memory_format);

  slow_conv2d_shape_check(
      self,
      Tensor(),
      weight_2d,
      bias,
      kernel_height,
      kernel_width,
      stride_height,
      stride_width,
      pad_height,
      pad_width,
      false);

  const Tensor input = self.contiguous(memory_format);
  const int64_t batch_size = input.size(0);
  const int64_t n_input_plane = input.size(1);
  const int64_t input_height = input.size(2);
  const int64_t input_width = input.size(3);
  const int64_t n_output_plane = weight_2d.size(0);
  const int64_t output_height = (input_height + 2 * pad_height - kernel_height) / stride_height + 1;
  const int64_t output_width = (input_width + 2 * pad_width - kernel_width) / stride_width + 1;

  Tensor finput = compute_columns2d(input, padding, stride, kernel_size, use_channels_last);
  output.resize_({batch_size, n_output_plane, output_height, output_width}, memory_format);
  if (bias.defined()) {
    output.copy_(bias.reshape({-1, 1, 1}));
  }
  TORCH_CHECK(output.is_contiguous(memory_format), "slow_conv2d output tensor must be contiguous");

  AT_DISPATCH_ALL_TYPES_AND2(kBFloat16, kHalf, input.scalar_type(), "slow_conv2d_cpu", [&amp;]{
    auto input_a = input.accessor&lt;scalar_t, 4&gt;();
    auto output_a = output.accessor&lt;scalar_t, 4&gt;();
    auto finput_a = finput.accessor&lt;scalar_t, 3&gt;();
    auto weight_2d_a = weight_2d.accessor&lt;scalar_t, 2&gt;();

    at::parallel_for(0, batch_size, 0, [&amp;](int64_t start, int64_t end) {
      for (const auto t : c10::irange(start, end)) {
        auto input_t = input_a[t];
        auto output_t = output_a[t];
        auto finput_t = finput_a[t];
        slow_conv2d_update_output_frame(
            input_t,
            output_t,
            weight_2d_a,
            bias.defined(),
            finput_t,
            kernel_height,
            kernel_width,
            stride_height,
            stride_width,
            pad_height,
            pad_width,
            n_input_plane,
            input_height,
            input_width,
            n_output_plane,
            output_height,
            output_width,
            use_channels_last);
      }
    });
  });

  return output;
}

Tensor slow_conv2d_forward_cpu_(
    const Tensor&amp; self,
    const Tensor&amp; weight,
    IntArrayRef kernel_size, const c10::optional&lt;Tensor&gt;&amp; bias_opt,
    IntArrayRef stride,
    IntArrayRef padding) {
  // See [Note: hacky wrapper removal for optional tensor]
  c10::MaybeOwned&lt;Tensor&gt; bias_maybe_owned = at::borrow_from_optional_tensor(bias_opt);
  const Tensor&amp; bias = *bias_maybe_owned;

  auto output = at::empty({0}, self.options());
  at::native::slow_conv2d_forward_out_cpu(
      self,
      weight,
      kernel_size,
      bias,
      stride,
      padding,
      output);

  return output;
}

std::tuple&lt;Tensor&amp;, Tensor&amp;, Tensor&amp;&gt; slow_conv2d_backward_out_cpu(
    const Tensor&amp; grad_output,
    const Tensor&amp; self,
    const Tensor&amp; weight,
    IntArrayRef kernel_size,
    IntArrayRef stride,
    IntArrayRef padding,
    Tensor&amp; grad_input,
    Tensor&amp; grad_weight,
    Tensor&amp; grad_bias) {
  if (grad_input.defined()) {
    slow_conv2d_backward_out_cpu_template(
        grad_input,
        grad_output,
        self,
        weight,
        kernel_size,
        stride,
        padding);
  }

  if (grad_bias.defined()) {
    at::sum_out(grad_bias, grad_output, IntArrayRef{0, 2, 3});
  }

  if (grad_weight.defined()) {
    grad_weight.resize_(weight.sizes(), weight.suggest_memory_format());
    grad_weight.zero_();
    slow_conv2d_backward_weight_out_cpu_template(
        grad_weight,
        self,
        grad_output,
        kernel_size,
        stride,
        padding);
  }

  return std::tuple&lt;Tensor&amp;, Tensor&amp;, Tensor&amp;&gt;(
      grad_input, grad_weight, grad_bias);
}

std::tuple&lt;Tensor, Tensor, Tensor&gt; slow_conv2d_backward_cpu_(
    const Tensor&amp; grad_output,
    const Tensor&amp; self,
    const Tensor&amp; weight,
    IntArrayRef kernel_size,
    IntArrayRef stride,
    IntArrayRef padding,
    std::array&lt;bool, 3&gt; output_mask) {
  Tensor grad_input;
  Tensor grad_weight;
  Tensor grad_bias;

  if (output_mask[0]) {
    grad_input = at::empty({0}, grad_output.options());
  }

  if (output_mask[1]) {
    grad_weight = at::empty({0}, grad_output.options());
  }

  if (output_mask[2]) {
    grad_bias = at::empty({0}, grad_output.options());
  }

  at::native::slow_conv2d_backward_out_cpu(
      grad_output,
      self,
      weight,
      kernel_size,
      stride,
      padding,
      grad_input,
      grad_weight,
      grad_bias);

  return std::make_tuple(grad_input, grad_weight, grad_bias);
}

Tensor &amp; thnn_conv2d_out(const Tensor &amp; self, const Tensor &amp; weight, IntArrayRef kernel_size, const c10::optional&lt;Tensor&gt;&amp; bias_opt, IntArrayRef stride, IntArrayRef padding, Tensor &amp; output) {
  // See [Note: hacky wrapper removal for optional tensor]
  c10::MaybeOwned&lt;Tensor&gt; bias_maybe_owned = at::borrow_from_optional_tensor(bias_opt);
  const Tensor&amp; bias = *bias_maybe_owned;

  return at::_slow_conv2d_forward_out(output, self, weight, kernel_size, bias, stride, padding);
}

Tensor thnn_conv2d(const Tensor &amp; self, const Tensor &amp; weight, IntArrayRef kernel_size, const c10::optional&lt;Tensor&gt;&amp; bias_opt, IntArrayRef stride, IntArrayRef padding) {
  // See [Note: hacky wrapper removal for optional tensor]
  c10::MaybeOwned&lt;Tensor&gt; bias_maybe_owned = at::borrow_from_optional_tensor(bias_opt);
  const Tensor&amp; bias = *bias_maybe_owned;

  return at::_slow_conv2d_forward(self, weight, kernel_size, bias, stride, padding);
}
PYBIND11_MODULE(TORCH_EXTENSION_NAME, m) {
  m.def("forward", &amp;slow_conv2d_forward_cpu_, "conv2dPerf forward");
  m.def("backward", &amp;slow_conv2d_backward_cpu_, "conv2dPerf backward");
}
} // namespace at::native
</code></pre>
<p>Any ideas how to solve this? Thanks in advance.</p>

71M073J: <p>This seems to be the same issue as mentioned <a href="https://discuss.pytorch.org/t/how-can-i-use-the-function-at-gemm-float/95341">here</a>, with advice to not use such low-level functions at all. There isn’t, however, any workaround for the cases when usage of those is necessary!</p>