gpt-5.5-priority · original · l123_single_gpt
correct 46_Average_Pooling_3D.py
model
gpt-5.5-priority
turns / max
1 / 1
tool calls
1
tokens (total)
0
wall clock
—
truncated
no
backend
cuda fp32
variant
original
runtime
4.91 μs
ref runtime
8.67 μs
speedup
1.77x
started
2026-05-05T00:30:58Z
timingkernelbench.timing
kernel runtime
4.91 μs
ref runtime
8.67 μs
speedup
1.77x
kernel mean / median / std
4.91 / — / 0.50 μs
ref mean / median / std
8.67 / — / 0.11 μs
numerical precisioneval_kernel_against_ref
max abs error
2.98e-07
mean abs error
3.07e-08
max rel error
5.64e-07
mean rel error
6.17e-08
gpu memoryextended_metrics.measure_memory
kernel peak
9249.00 MB
ref peak
9249.00 MB
kernel / ref
1.00x
kernel launches / fusiontorch.profiler
kernel launches
1
ref launches
5
fusion ratio (ref / kernel)
5.00x
total cuda time
31 μs
energyNVML (pynvml)
kernel mJ / run
725.84
ref mJ / run
1935.78
ratio (ref / kernel)
2.67x
avg power
149.54 W
ref avg power
223.57 W
SOL (speed-of-light)nsight_hardware_counters
SOL score (0–1)
0.655
dram util %
65.5%
compute util %
17.7%
bottleneck
memory-bound
dominant pipe
FP32
arithmetic intensity
—
ridge point
20.00
roofline / occupancynsight_hardware_counters
dram bandwidth
2194.20 GB/s
dram util %
65.5%
fp32 tflops
—
fp32 util %
—
fp16 tflops
—
occupancy %
49.1%
bottleneck
memory-bound
dominant pipe %
17.7%
arithmetic intensity
—
ridge point
20.00
L1 hit %
—
L2 hit %
29.4%
ld sectors / req
10.11
st sectors / req
—
regs / thread
56
smem / block
1024 B
block size
128
peak bw
3350.00 GB/s
peak fp32 tflops
67.00
peak fp16 tflops
1979.00
warp stalls (top 5)nsight
long_scoreboard
886.7%
short_scoreboard
8.3%
mio_throttle
0.2%
reference PyTorch source (the prompt the agent sees)
import torch
import torch.nn as nn

class Model(nn.Module):
    """
    Simple model that performs 3D Average Pooling.
    """
    def __init__(self, kernel_size: int, stride: int = None, padding: int = 0):
        """
        Initializes the Average Pooling layer.

        Args:
            kernel_size (int): Size of the kernel to apply pooling.
            stride (int, optional): Stride of the pooling operation. Defaults to None, which uses the kernel size.
            padding (int, optional): Padding to apply before pooling. Defaults to 0.
        """
        super(Model, self).__init__()
        self.avg_pool = nn.AvgPool3d(kernel_size=kernel_size, stride=stride, padding=padding)

    def forward(self, x: torch.Tensor) -> torch.Tensor:
        """
        Applies Average Pooling to the input tensor.

        Args:
            x (torch.Tensor): Input tensor of shape (batch_size, channels, depth, height, width).

        Returns:
            torch.Tensor: Output tensor with Average Pooling applied, shape depends on kernel_size, stride and padding.
        """
        return self.avg_pool(x)

batch_size = 16
channels = 32
depth = 128
height = 128
width = 256
kernel_size = 3
stride = 2
padding = 1

def get_inputs():
    x = torch.rand(batch_size, channels, depth, height, width)
    return [x]

def get_init_inputs():
    return [kernel_size, stride, padding]
turn 0 · FINAL · 1 tool call(s) · 101.1s turn · 101.1s LLM · 11,213 chars (click to expand)
turn 0101.1s turn · 101.1s LLM · 0.0s tools · FINAL
reasoning
**Developing AvgPool3D Module** I need to create a Python module for AvgPool3D with fixed likely arguments but a general constructor. It seems the input shape would be huge when considering 16*32*128*128*256, amounting to over 8GB. The output would be calculated based on the pooling layers, but I should ensure it's optimized. I’ll look into PyTorch's nn.AvgPool3d settings for correct padding and kernel size handling while ensuring efficiency in float precision and support for various input dimensions. **Building C++ Wrapper for AvgPool3D** I need to create a complete file for loading inline and build a C++ wrapper that determines output shape and launches properly. Including ATen would help, and I won't use PYBIND. I should focus on optimizing the kernel since the output is significantly large, with each sample needing substantial bandwidth. There could be performance issues with the generic avgpool, so I’ll aim for a custom and faster approach, leveraging separate kernels only when necessary for specialized cases.
call → submit_kernel
callsubmit_kernel { "kernel_code": "import torch\nimport torch.nn as nn\nfrom torch.utils.cpp_extension import load_inline\n\n_cpp_src = r'''\n#include <torch/extension.h>\n\ntorch::Tensor avgpool3d_forward_cuda(torch::Tensor x, int64_t k, int64_t s, int64_t p);\n\ntorch::Tensor avgpool3d_forward(torch::Tensor x, int64_t k, int64_t s, int64_t p) {\n return avgpool3d_forward_cuda(x, k, s, p);\n}\n'''\n\n_cuda_src = r'''\n#include <torch/extension.h>\n#include <ATen/cuda/CUDAContext.h>\n#include <c10/cuda/CUDAException.h>\n\n__device__ __forceinline__ float ldg_f(const float* p) {\n#if __CUDA_ARCH__ >= 350\n return __ldg(p);\n#else\n return *p;\n#endif\n}\n\n__global__ __launch_bounds__(128)\nvoid avgpool3d_k3s2p1_tiled_kernel(const float* __restrict__ x,\n float* __restrict__ y,\n int N, int C, int D, int H, int W,\n int Do, int Ho, int Wo,\n int Dp, int Hp, int Wp,\n long long total_tiles) {\n long long tid = (long long)blockIdx.x * blockDim.x + threadIdx.x;\n if (tid >= total_tiles) return;\n\n int wp = (int)(tid % Wp);\n long long q = tid / Wp;\n int hp = (int)(q % Hp);\n q /= Hp;\n int dp = (int)(q % Dp);\n long long nc = q / Dp;\n\n int od0 = dp << 1;\n int oh0 = hp << 1;\n int ow0 = wp << 1;\n int iz0 = (od0 << 1) - 1;\n int iy0 = (oh0 << 1) - 1;\n int ix0 = (ow0 << 1) - 1;\n\n const long long DHW = (long long)D * H * W;\n const long long HoWo = (long long)Ho * Wo;\n const long long base_in = nc * DHW;\n const long long base_out = nc * (long long)Do * HoWo;\n const float inv27 = 0.037037037037037035f;\n\n // Fast path for the overwhelmingly common, fully-valid 2x2x2 output tile.\n if (od0 + 1 < Do && oh0 + 1 < Ho && ow0 + 1 < Wo &&\n iz0 >= 0 && iy0 >= 0 && ix0 >= 0 &&\n iz0 + 4 < D && iy0 + 4 < H && ix0 + 4 < W) {\n\n float s000 = 0.f, s001 = 0.f, s010 = 0.f, s011 = 0.f;\n float s100 = 0.f, s101 = 0.f, s110 = 0.f, s111 = 0.f;\n\n #pragma unroll\n for (int zz = 0; zz < 5; ++zz) {\n float q00 = 0.f, q01 = 0.f, q10 = 0.f, q11 = 0.f;\n const long long zbase = base_in + (long long)(iz0 + zz) * H * W + (long long)iy0 * W + ix0;\n\n #pragma unroll\n for (int yy = 0; yy < 5; ++yy) {\n const float* row = x + zbase + (long long)yy * W;\n float v0 = ldg_f(row + 0);\n float v1 = ldg_f(row + 1);\n float v2 = ldg_f(row + 2);\n float v3 = ldg_f(row + 3);\n float v4 = ldg_f(row + 4);\n float a = v0 + v1 + v2;\n float b = v2 + v3 + v4;\n if (yy < 3) { q00 += a; q01 += b; }\n if (yy >= 2) { q10 += a; q11 += b; }\n }\n if (zz < 3) { s000 += q00; s001 += q01; s010 += q10; s011 += q11; }\n if (zz >= 2) { s100 += q00; s101 += q01; s110 += q10; s111 += q11; }\n }\n\n long long o_d0 = base_out + (long long)od0 * HoWo;\n long long o_d1 = o_d0 + HoWo;\n long long o_h0 = (long long)oh0 * Wo + ow0;\n long long o_h1 = o_h0 + Wo;\n y[o_d0 + o_h0] = s000 * inv27;\n y[o_d0 + o_h0 + 1] = s001 * inv27;\n y[o_d0 + o_h1] = s010 * inv27;\n y[o_d0 + o_h1 + 1] = s011 * inv27;\n y[o_d1 + o_h0] = s100 * inv27;\n y[o_d1 + o_h0 + 1] = s101 * inv27;\n y[o_d1 + o_h1] = s110 * inv27;\n y[o_d1 + o_h1 + 1] = s111 * inv27;\n return;\n }\n\n // Boundary/odd-size path, still count_include_pad=True (fixed divisor 27).\n #pragma unroll\n for (int dz = 0; dz < 2; ++dz) {\n int od = od0 + dz;\n if (od >= Do) continue;\n int izs = (od << 1) - 1;\n #pragma unroll\n for (int dy = 0; dy < 2; ++dy) {\n int oh = oh0 + dy;\n if (oh >= Ho) continue;\n int iys = (oh << 1) - 1;\n #pragma unroll\n for (int dx = 0; dx < 2; ++dx) {\n int ow = ow0 + dx;\n if (ow >= Wo) continue;\n int ixs = (ow << 1) - 1;\n float sum = 0.f;\n #pragma unroll\n for (int kz = 0; kz < 3; ++kz) {\n int iz = izs + kz;\n if ((unsigned)iz >= (unsigned)D) continue;\n #pragma unroll\n for (int ky = 0; ky < 3; ++ky) {\n int iy = iys + ky;\n if ((unsigned)iy >= (unsigned)H) continue;\n const float* row = x + base_in + (long long)iz * H * W + (long long)iy * W;\n #pragma unroll\n for (int kx = 0; kx < 3; ++kx) {\n int ix = ixs + kx;\n if ((unsigned)ix < (unsigned)W) sum += ldg_f(row + ix);\n }\n }\n }\n y[base_out + (long long)od * HoWo + (long long)oh * Wo + ow] = sum * inv27;\n }\n }\n }\n}\n\n__global__ __launch_bounds__(256)\nvoid avgpool3d_generic_kernel(const float* __restrict__ x,\n float* __restrict__ y,\n int D, int H, int W, int Do, int Ho, int Wo,\n int k, int s, int p, long long total) {\n long long idx = (long long)blockIdx.x * blockDim.x + threadIdx.x;\n if (idx >= total) return;\n int ow = (int)(idx % Wo);\n long long q = idx / Wo;\n int oh = (int)(q % Ho); q /= Ho;\n int od = (int)(q % Do); q /= Do;\n long long nc = q;\n long long base = nc * (long long)D * H * W;\n int izs = od * s - p;\n int iys = oh * s - p;\n int ixs = ow * s - p;\n float sum = 0.f;\n for (int kz = 0; kz < k; ++kz) {\n int iz = izs + kz;\n if ((unsigned)iz >= (unsigned)D) continue;\n for (int ky = 0; ky < k; ++ky) {\n int iy = iys + ky;\n if ((unsigned)iy >= (unsigned)H) continue;\n const float* row = x + base + (long long)iz * H * W + (long long)iy * W;\n for (int kx = 0; kx < k; ++kx) {\n int ix = ixs + kx;\n if ((unsigned)ix < (unsigned)W) sum += ldg_f(row + ix);\n }\n }\n }\n y[idx] = sum / (float)(k * k * k);\n}\n\ntorch::Tensor avgpool3d_forward_cuda(torch::Tensor x, int64_t k64, int64_t s64, int64_t p64) {\n TORCH_CHECK(x.is_cuda(), \"input must be CUDA\");\n TORCH_CHECK(x.scalar_type() == at::kFloat, \"only float32 is supported\");\n TORCH_CHECK(x.dim() == 5, \"expected 5D NCDHW input\");\n\n int N = (int)x.size(0), C = (int)x.size(1), D = (int)x.size(2), H = (int)x.size(3), W = (int)x.size(4);\n int k = (int)k64, s = (int)s64, p = (int)p64;\n int Do = (D + 2 * p - k) / s + 1;\n int Ho = (H + 2 * p - k) / s + 1;\n int Wo = (W + 2 * p - k) / s + 1;\n auto y = torch::empty({N, C, Do, Ho, Wo}, x.options());\n\n const float* xp = x.data_ptr<float>();\n float* yp = y.data_ptr<float>();\n cudaStream_t stream = at::cuda::getCurrentCUDAStream();\n\n if (k == 3 && s == 2 && p == 1) {\n int Dp = (Do + 1) >> 1;\n int Hp = (Ho + 1) >> 1;\n int Wp = (Wo + 1) >> 1;\n long long total_tiles = (long long)N * C * Dp * Hp * Wp;\n dim3 block(128);\n dim3 grid((unsigned int)((total_tiles + block.x - 1) / block.x));\n avgpool3d_k3s2p1_tiled_kernel<<<grid, block, 0, stream>>>(xp, yp, N, C, D, H, W, Do, Ho, Wo, Dp, Hp, Wp, total_tiles);\n } else {\n long long total = (long long)N * C * Do * Ho * Wo;\n dim3 block(256);\n dim3 grid((unsigned int)((total + block.x - 1) / block.x));\n avgpool3d_generic_kernel<<<grid, block, 0, stream>>>(xp, yp, D, H, W, Do, Ho, Wo, k, s, p, total);\n }\n C10_CUDA_KERNEL_LAUNCH_CHECK();\n return y;\n}\n'''\n\n_ext = load_inline(\n name='avgpool3d_custom_ext_v1',\n cpp_sources=_cpp_src,\n cuda_sources=_cuda_src,\n functions=['avgpool3d_forward'],\n extra_cflags=['-O3'],\n extra_cuda_cflags=['-O3'],\n verbose=False,\n)\n\nclass ModelNew(nn.Module):\n def __init__(self, kernel_size: int, stride: int = None, padding: int = 0):\n super(ModelNew, self).__init__()\n self.kernel_size = int(kernel_size)\n self.stride = int(kernel_size if stride is None else stride)\n self.padding = int(padding)\n\n def forward(self, x: torch.Tensor) -> torch.Tensor:\n return _ext.avgpool3d_forward(x, self.kernel_size, self.stride, self.padding)\n" }
result ← submit_kernel (ok)
oksubmit_kernel submit_kernel PASSED: (5 / 5) correctness trials all passed. Kernel runtime: 4.91 μs Runtime stats: mean=4.91μs median=?μs std=0.50μs Numerical precision: max_abs_err=2.98e-07 mean_abs_err=3.07e-08 max_rel_err=5.64e-07 Memory: 9249.0 MB peak (ref: 9249.0 MB, ratio: 1.0x) Kernel launches: 1 (ref: 5, fusion ratio: 5.0) Energy: 725.84 mJ/run (ref: 1935.78 mJ, ratio: 2.667x) SOL score: 0.655
final submitted kernel (229 lines, 8,489 chars) — full source, untruncated
import torch
import torch.nn as nn
from torch.utils.cpp_extension import load_inline

_cpp_src = r'''
#include <torch/extension.h>

torch::Tensor avgpool3d_forward_cuda(torch::Tensor x, int64_t k, int64_t s, int64_t p);

torch::Tensor avgpool3d_forward(torch::Tensor x, int64_t k, int64_t s, int64_t p) {
    return avgpool3d_forward_cuda(x, k, s, p);
}
'''

_cuda_src = r'''
#include <torch/extension.h>
#include <ATen/cuda/CUDAContext.h>
#include <c10/cuda/CUDAException.h>

__device__ __forceinline__ float ldg_f(const float* p) {
#if __CUDA_ARCH__ >= 350
    return __ldg(p);
#else
    return *p;
#endif
}

__global__ __launch_bounds__(128)
void avgpool3d_k3s2p1_tiled_kernel(const float* __restrict__ x,
                                    float* __restrict__ y,
                                    int N, int C, int D, int H, int W,
                                    int Do, int Ho, int Wo,
                                    int Dp, int Hp, int Wp,
                                    long long total_tiles) {
    long long tid = (long long)blockIdx.x * blockDim.x + threadIdx.x;
    if (tid >= total_tiles) return;

    int wp = (int)(tid % Wp);
    long long q = tid / Wp;
    int hp = (int)(q % Hp);
    q /= Hp;
    int dp = (int)(q % Dp);
    long long nc = q / Dp;

    int od0 = dp << 1;
    int oh0 = hp << 1;
    int ow0 = wp << 1;
    int iz0 = (od0 << 1) - 1;
    int iy0 = (oh0 << 1) - 1;
    int ix0 = (ow0 << 1) - 1;

    const long long DHW = (long long)D * H * W;
    const long long HoWo = (long long)Ho * Wo;
    const long long base_in = nc * DHW;
    const long long base_out = nc * (long long)Do * HoWo;
    const float inv27 = 0.037037037037037035f;

    // Fast path for the overwhelmingly common, fully-valid 2x2x2 output tile.
    if (od0 + 1 < Do && oh0 + 1 < Ho && ow0 + 1 < Wo &&
        iz0 >= 0 && iy0 >= 0 && ix0 >= 0 &&
        iz0 + 4 < D && iy0 + 4 < H && ix0 + 4 < W) {

        float s000 = 0.f, s001 = 0.f, s010 = 0.f, s011 = 0.f;
        float s100 = 0.f, s101 = 0.f, s110 = 0.f, s111 = 0.f;

        #pragma unroll
        for (int zz = 0; zz < 5; ++zz) {
            float q00 = 0.f, q01 = 0.f, q10 = 0.f, q11 = 0.f;
            const long long zbase = base_in + (long long)(iz0 + zz) * H * W + (long long)iy0 * W + ix0;

            #pragma unroll
            for (int yy = 0; yy < 5; ++yy) {
                const float* row = x + zbase + (long long)yy * W;
                float v0 = ldg_f(row + 0);
                float v1 = ldg_f(row + 1);
                float v2 = ldg_f(row + 2);
                float v3 = ldg_f(row + 3);
                float v4 = ldg_f(row + 4);
                float a = v0 + v1 + v2;
                float b = v2 + v3 + v4;
                if (yy < 3) { q00 += a; q01 += b; }
                if (yy >= 2) { q10 += a; q11 += b; }
            }
            if (zz < 3) { s000 += q00; s001 += q01; s010 += q10; s011 += q11; }
            if (zz >= 2) { s100 += q00; s101 += q01; s110 += q10; s111 += q11; }
        }

        long long o_d0 = base_out + (long long)od0 * HoWo;
        long long o_d1 = o_d0 + HoWo;
        long long o_h0 = (long long)oh0 * Wo + ow0;
        long long o_h1 = o_h0 + Wo;
        y[o_d0 + o_h0]     = s000 * inv27;
        y[o_d0 + o_h0 + 1] = s001 * inv27;
        y[o_d0 + o_h1]     = s010 * inv27;
        y[o_d0 + o_h1 + 1] = s011 * inv27;
        y[o_d1 + o_h0]     = s100 * inv27;
        y[o_d1 + o_h0 + 1] = s101 * inv27;
        y[o_d1 + o_h1]     = s110 * inv27;
        y[o_d1 + o_h1 + 1] = s111 * inv27;
        return;
    }

    // Boundary/odd-size path, still count_include_pad=True (fixed divisor 27).
    #pragma unroll
    for (int dz = 0; dz < 2; ++dz) {
        int od = od0 + dz;
        if (od >= Do) continue;
        int izs = (od << 1) - 1;
        #pragma unroll
        for (int dy = 0; dy < 2; ++dy) {
            int oh = oh0 + dy;
            if (oh >= Ho) continue;
            int iys = (oh << 1) - 1;
            #pragma unroll
            for (int dx = 0; dx < 2; ++dx) {
                int ow = ow0 + dx;
                if (ow >= Wo) continue;
                int ixs = (ow << 1) - 1;
                float sum = 0.f;
                #pragma unroll
                for (int kz = 0; kz < 3; ++kz) {
                    int iz = izs + kz;
                    if ((unsigned)iz >= (unsigned)D) continue;
                    #pragma unroll
                    for (int ky = 0; ky < 3; ++ky) {
                        int iy = iys + ky;
                        if ((unsigned)iy >= (unsigned)H) continue;
                        const float* row = x + base_in + (long long)iz * H * W + (long long)iy * W;
                        #pragma unroll
                        for (int kx = 0; kx < 3; ++kx) {
                            int ix = ixs + kx;
                            if ((unsigned)ix < (unsigned)W) sum += ldg_f(row + ix);
                        }
                    }
                }
                y[base_out + (long long)od * HoWo + (long long)oh * Wo + ow] = sum * inv27;
            }
        }
    }
}

__global__ __launch_bounds__(256)
void avgpool3d_generic_kernel(const float* __restrict__ x,
                              float* __restrict__ y,
                              int D, int H, int W, int Do, int Ho, int Wo,
                              int k, int s, int p, long long total) {
    long long idx = (long long)blockIdx.x * blockDim.x + threadIdx.x;
    if (idx >= total) return;
    int ow = (int)(idx % Wo);
    long long q = idx / Wo;
    int oh = (int)(q % Ho); q /= Ho;
    int od = (int)(q % Do); q /= Do;
    long long nc = q;
    long long base = nc * (long long)D * H * W;
    int izs = od * s - p;
    int iys = oh * s - p;
    int ixs = ow * s - p;
    float sum = 0.f;
    for (int kz = 0; kz < k; ++kz) {
        int iz = izs + kz;
        if ((unsigned)iz >= (unsigned)D) continue;
        for (int ky = 0; ky < k; ++ky) {
            int iy = iys + ky;
            if ((unsigned)iy >= (unsigned)H) continue;
            const float* row = x + base + (long long)iz * H * W + (long long)iy * W;
            for (int kx = 0; kx < k; ++kx) {
                int ix = ixs + kx;
                if ((unsigned)ix < (unsigned)W) sum += ldg_f(row + ix);
            }
        }
    }
    y[idx] = sum / (float)(k * k * k);
}

torch::Tensor avgpool3d_forward_cuda(torch::Tensor x, int64_t k64, int64_t s64, int64_t p64) {
    TORCH_CHECK(x.is_cuda(), "input must be CUDA");
    TORCH_CHECK(x.scalar_type() == at::kFloat, "only float32 is supported");
    TORCH_CHECK(x.dim() == 5, "expected 5D NCDHW input");

    int N = (int)x.size(0), C = (int)x.size(1), D = (int)x.size(2), H = (int)x.size(3), W = (int)x.size(4);
    int k = (int)k64, s = (int)s64, p = (int)p64;
    int Do = (D + 2 * p - k) / s + 1;
    int Ho = (H + 2 * p - k) / s + 1;
    int Wo = (W + 2 * p - k) / s + 1;
    auto y = torch::empty({N, C, Do, Ho, Wo}, x.options());

    const float* xp = x.data_ptr<float>();
    float* yp = y.data_ptr<float>();
    cudaStream_t stream = at::cuda::getCurrentCUDAStream();

    if (k == 3 && s == 2 && p == 1) {
        int Dp = (Do + 1) >> 1;
        int Hp = (Ho + 1) >> 1;
        int Wp = (Wo + 1) >> 1;
        long long total_tiles = (long long)N * C * Dp * Hp * Wp;
        dim3 block(128);
        dim3 grid((unsigned int)((total_tiles + block.x - 1) / block.x));
        avgpool3d_k3s2p1_tiled_kernel<<<grid, block, 0, stream>>>(xp, yp, N, C, D, H, W, Do, Ho, Wo, Dp, Hp, Wp, total_tiles);
    } else {
        long long total = (long long)N * C * Do * Ho * Wo;
        dim3 block(256);
        dim3 grid((unsigned int)((total + block.x - 1) / block.x));
        avgpool3d_generic_kernel<<<grid, block, 0, stream>>>(xp, yp, D, H, W, Do, Ho, Wo, k, s, p, total);
    }
    C10_CUDA_KERNEL_LAUNCH_CHECK();
    return y;
}
'''

_ext = load_inline(
    name='avgpool3d_custom_ext_v1',
    cpp_sources=_cpp_src,
    cuda_sources=_cuda_src,
    functions=['avgpool3d_forward'],
    extra_cflags=['-O3'],
    extra_cuda_cflags=['-O3'],
    verbose=False,
)

class ModelNew(nn.Module):
    def __init__(self, kernel_size: int, stride: int = None, padding: int = 0):
        super(ModelNew, self).__init__()
        self.kernel_size = int(kernel_size)
        self.stride = int(kernel_size if stride is None else stride)
        self.padding = int(padding)

    def forward(self, x: torch.Tensor) -> torch.Tensor:
        return _ext.avgpool3d_forward(x, self.kernel_size, self.stride, self.padding)