gpt-5.5-priority · original · l123_single_gpt
compile_fail 46_NetVladWithGhostClusters.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
—
ref runtime
-1.00 μs
speedup
—
started
2026-05-05T00:59:54Z
compile error: Error building extension 'netvlad_post_ext_v1': [1/3] /opt/conda/bin/x86_64-conda-linux-gnu-c++ -MMD -MF main.o.d -DTORCH_EXTENSION_NAME=netvlad_post_ext_v1 -DTORCH_API_INCLUDE_EXTENSION_H -isystem /scratch/abaner/KernelBench/.venv/lib/python3.10/site-packages/torch/include -isystem /scratch/abaner/KernelBench/.venv/lib/python3.10/site-packages/torch/include/torch/csrc/api/include -isystem /usr/local/cuda/include -isystem /opt/conda/include/python3.10 -fPIC -std=c++17 -O3 -c /scratch/abaner/KernelBench/runs/l123_single_gpt/original/gpt-5.5-priority/level_3_problem_46_cache/k_35bde5580302/netvl
timingkernelbench.timing
kernel runtime
—
ref runtime
—
speedup
—
kernel mean / median / std
—
ref mean / median / std
—
reference PyTorch source (the prompt the agent sees)
# Copyright 2018 Antoine Miech All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS-IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""
Code modified from here
https://github.com/albanie/collaborative-experts/blob/master/model/net_vlad.py
"""


import math
import torch
import torch.nn as nn
import torch.nn.functional as F
import torch as th


class Model(nn.Module):
    def __init__(self, cluster_size, feature_size, ghost_clusters):
        super(Model, self).__init__()

        self.feature_size = feature_size
        self.cluster_size = cluster_size
        self.ghost_clusters = ghost_clusters

        init_sc = (1 / math.sqrt(feature_size))
        clusters = cluster_size + ghost_clusters

        # The `clusters` weights are the `(w,b)` in the paper
        self.clusters = nn.Parameter(init_sc * th.randn(feature_size, clusters))
        self.batch_norm = nn.BatchNorm1d(clusters)
        # The `clusters2` weights are the visual words `c_k` in the paper
        self.clusters2 = nn.Parameter(init_sc * th.randn(1, feature_size, cluster_size))
        self.out_dim = self.cluster_size * feature_size

    def forward(self, x, mask=None):
        """Aggregates feature maps into a fixed size representation.  In the following
        notation, B = batch_size, N = num_features, K = num_clusters, D = feature_size.

        Args:
            x (th.Tensor): B x N x D

        Returns:
            (th.Tensor): B x DK
        """
        max_sample = x.size()[1]
        x = x.view(-1, self.feature_size)  # B x N x D -> BN x D

        if x.device != self.clusters.device:
            msg = f"x.device {x.device} != cluster.device {self.clusters.device}"
            raise ValueError(msg)

        assignment = th.matmul(x, self.clusters)  # (BN x D) x (D x (K+G)) -> BN x (K+G)
        assignment = self.batch_norm(assignment)

        assignment = F.softmax(assignment, dim=1)  # BN x (K+G) -> BN x (K+G)
        # remove ghost assigments
        assignment = assignment[:, :self.cluster_size]
        assignment = assignment.view(-1, max_sample, self.cluster_size)  # -> B x N x K
        a_sum = th.sum(assignment, dim=1, keepdim=True)  # B x N x K -> B x 1 x K
        a = a_sum * self.clusters2

        assignment = assignment.transpose(1, 2)  # B x N x K -> B x K x N

        x = x.view(-1, max_sample, self.feature_size)  # BN x D -> B x N x D
        vlad = th.matmul(assignment, x)  # (B x K x N) x (B x N x D) -> B x K x D
        vlad = vlad.transpose(1, 2)  # -> B x D x K
        vlad = vlad - a

        # L2 intra norm
        vlad = F.normalize(vlad)

        # flattening + L2 norm
        vlad = vlad.reshape(-1, self.cluster_size * self.feature_size)  # -> B x DK
        vlad = F.normalize(vlad)
        return vlad  # B x DK

batch_size = 2048
num_features = 100
num_clusters = 32
feature_size = 512
ghost_clusters = 16

def get_inputs():
  return [torch.rand(batch_size, num_features, feature_size)]

def get_init_inputs():
  return [num_clusters, feature_size, ghost_clusters]
turn 0 · FINAL · 1 tool call(s) · 82.4s turn · 82.4s LLM · 12,718 chars (click to expand)
turn 082.4s turn · 82.4s LLM · 0.0s tools · FINAL
reasoning
**Optimizing matrix operations** I'm exploring PyTorch's reference for matmul, which possibly handles around 5 billion ops, and aggregation at about 3.3 billion. If I need speed, a custom naive matmul might be slower. I wonder if I can use torch operations instead? A requirement is a custom CUDA kernel for the forward pass, but could torch.mm be efficient? I might rely on cuBLAS for matmul and apply custom aggregation. Lastly, I need to formulate the model and consider how torch.matmul integrates with it. **Calculating normalization** I'm thinking about how to normalize need values. It seems like I could compute values twice — once for the normalization and then again to write them, without needing any temporary storage. I wonder if this approach will be efficient enough without adding unnecessary complexity. It’s crucial to get the calculations right while keeping the process as streamlined as possible! Let's ensure I'm clear on the steps needed before moving forward. **Clarifying CUDA sources** I’m thinking about how to handle the CUDA sources and C++ declaration. We can put everything in cuda_sources, and the function declaration would go in cpp. I see that load_inline generates the necessary bindings in C++, including that declaration. It looks like we also need to define the function in cpp_sources with `torch::Tensor post_forward(torch::Tensor x, torch::Tensor assignment, torch::Tensor clusters2, int64_t cluster_size);`, while cuda_sources would handle its definition. No PYBIND is required, which is good!
call → submit_kernel
callsubmit_kernel { "kernel_code": "import math\nimport torch\nimport torch.nn as nn\nimport torch.nn.functional as F\nfrom torch.utils.cpp_extension import load_inline\n\n\n_cpp_src = r\"\"\"\n#include <torch/extension.h>\n\ntorch::Tensor post_forward(torch::Tensor x, torch::Tensor assignment, torch::Tensor clusters2, int64_t cluster_size);\n\"\"\"\n\n_cuda_src = r\"\"\"\n#include <torch/extension.h>\n#include <ATen/cuda/CUDAContext.h>\n#include <c10/cuda/CUDAGuard.h>\n#include <c10/cuda/CUDAException.h>\n#include <cuda.h>\n#include <cuda_runtime.h>\n#include <math.h>\n\n__global__ void softmax_store_kernel(const float* __restrict__ a,\n float* __restrict__ p,\n float* __restrict__ asum,\n int rows, int C, int K, int N) {\n int row = blockIdx.x;\n if (row >= rows) return;\n int tid = threadIdx.x;\n extern __shared__ float smem[];\n\n float mx = -CUDART_INF_F;\n const float* ar = a + (long long)row * C;\n for (int c = tid; c < C; c += blockDim.x) {\n float v = ar[c];\n mx = fmaxf(mx, v);\n }\n smem[tid] = mx;\n __syncthreads();\n for (int s = blockDim.x >> 1; s > 0; s >>= 1) {\n if (tid < s) smem[tid] = fmaxf(smem[tid], smem[tid + s]);\n __syncthreads();\n }\n mx = smem[0];\n\n float sum = 0.0f;\n for (int c = tid; c < C; c += blockDim.x) {\n sum += expf(ar[c] - mx);\n }\n smem[tid] = sum;\n __syncthreads();\n for (int s = blockDim.x >> 1; s > 0; s >>= 1) {\n if (tid < s) smem[tid] += smem[tid + s];\n __syncthreads();\n }\n float inv = 1.0f / smem[0];\n\n int b = row / N;\n int n = row - b * N;\n float* pr = p + ((long long)b * N + n) * K;\n float* as = asum + (long long)b * K;\n for (int k = tid; k < K; k += blockDim.x) {\n float pv = expf(ar[k] - mx) * inv;\n pr[k] = pv;\n atomicAdd(as + k, pv);\n }\n}\n\n__global__ void vlad_bmm_kernel(const float* __restrict__ x,\n const float* __restrict__ p,\n const float* __restrict__ asum,\n const float* __restrict__ c2,\n float* __restrict__ tmp,\n int B, int N, int D, int K) {\n int d = blockIdx.x * 16 + threadIdx.x;\n int k = blockIdx.y * 16 + threadIdx.y;\n int b = blockIdx.z;\n if (d >= D || k >= K || b >= B) return;\n\n float acc = 0.0f;\n const float* xb = x + (long long)b * N * D + d;\n const float* pb = p + (long long)b * N * K + k;\n #pragma unroll 4\n for (int n = 0; n < N; ++n) {\n acc += pb[(long long)n * K] * xb[(long long)n * D];\n }\n acc -= asum[(long long)b * K + k] * c2[(long long)d * K + k];\n tmp[((long long)b * K + k) * D + d] = acc;\n}\n\n__global__ void norm_output_kernel(const float* __restrict__ tmp,\n float* __restrict__ out,\n int B, int D, int K) {\n int b = blockIdx.x;\n int k = blockIdx.y;\n int tid = threadIdx.x;\n extern __shared__ float smem[];\n\n float ss = 0.0f;\n const float* base = tmp + ((long long)b * K + k) * D;\n for (int d = tid; d < D; d += blockDim.x) {\n float v = base[d];\n ss += v * v;\n }\n smem[tid] = ss;\n __syncthreads();\n for (int s = blockDim.x >> 1; s > 0; s >>= 1) {\n if (tid < s) smem[tid] += smem[tid + s];\n __syncthreads();\n }\n float norm = sqrtf(smem[0]);\n float scale = (norm > 1.0e-12f ? 1.0f / norm : 1.0e12f) * rsqrtf((float)K);\n float* ob = out + (long long)b * D * K + k;\n for (int d = tid; d < D; d += blockDim.x) {\n ob[(long long)d * K] = base[d] * scale;\n }\n}\n\ntorch::Tensor post_forward(torch::Tensor x, torch::Tensor assignment, torch::Tensor clusters2, int64_t cluster_size) {\n TORCH_CHECK(x.is_cuda(), \"x must be CUDA\");\n TORCH_CHECK(assignment.is_cuda(), \"assignment must be CUDA\");\n TORCH_CHECK(clusters2.is_cuda(), \"clusters2 must be CUDA\");\n TORCH_CHECK(x.scalar_type() == torch::kFloat32, \"only float32 supported\");\n TORCH_CHECK(assignment.scalar_type() == torch::kFloat32, \"only float32 supported\");\n c10::cuda::CUDAGuard guard(x.device());\n\n int B = (int)x.size(0);\n int N = (int)x.size(1);\n int D = (int)x.size(2);\n int C = (int)assignment.size(1);\n int K = (int)cluster_size;\n int rows = B * N;\n\n auto opts = x.options();\n auto probs = torch::empty({B, N, K}, opts);\n auto asum = torch::zeros({B, K}, opts);\n\n cudaStream_t stream = at::cuda::getCurrentCUDAStream();\n int sm_threads = 64;\n softmax_store_kernel<<<rows, sm_threads, sm_threads * sizeof(float), stream>>>(\n assignment.data_ptr<float>(), probs.data_ptr<float>(), asum.data_ptr<float>(), rows, C, K, N);\n\n auto tmp = torch::empty({B, K, D}, opts);\n dim3 block(16, 16, 1);\n dim3 grid((D + 15) / 16, (K + 15) / 16, B);\n vlad_bmm_kernel<<<grid, block, 0, stream>>>(\n x.data_ptr<float>(), probs.data_ptr<float>(), asum.data_ptr<float>(),\n clusters2.data_ptr<float>(), tmp.data_ptr<float>(), B, N, D, K);\n\n auto out = torch::empty({B, D * K}, opts);\n dim3 grid2(B, K, 1);\n int nt = 256;\n norm_output_kernel<<<grid2, nt, nt * sizeof(float), stream>>>(\n tmp.data_ptr<float>(), out.data_ptr<float>(), B, D, K);\n C10_CUDA_KERNEL_LAUNCH_CHECK();\n return out;\n}\n\"\"\"\n\n_ext = load_inline(\n name=\"netvlad_post_ext_v1\",\n cpp_sources=_cpp_src,\n cuda_sources=_cuda_src,\n functions=[\"post_forward\"],\n extra_cflags=[\"-O3\"],\n extra_cuda_cflags=[\"-O3\"],\n verbose=False,\n)\n\n\nclass ModelNew(nn.Module):\n def __init__(self, cluster_size, feature_size, ghost_clusters):\n super(ModelNew, self).__init__()\n self.feature_size = feature_size\n self.cluster_size = cluster_size\n self.ghost_clusters = ghost_clusters\n\n init_sc = (1 / math.sqrt(feature_size))\n clusters = cluster_size + ghost_clusters\n self.clusters = nn.Parameter(init_sc * torch.randn(feature_size, clusters))\n self.batch_norm = nn.BatchNorm1d(clusters)\n self.clusters2 = nn.Parameter(init_sc * torch.randn(1, feature_size, cluster_size))\n self.out_dim = self.cluster_size * feature_size\n\n def forward(self, x, mask=None):\n max_sample = x.size(1)\n x_contig = x.contiguous()\n x_flat = x_contig.view(-1, self.feature_size)\n if x_flat.device != self.clusters.device:\n msg = f\"x.device {x_flat.device} != cluster.device {self.clusters.device}\"\n raise ValueError(msg)\n assignment = torch.matmul(x_flat, self.clusters)\n assignment = self.batch_norm(assignment)\n assignment = assignment.contiguous()\n c2 = self.clusters2.contiguous()\n return _ext.post_forward(x_contig, assignment, c2, self.cluster_size)\n" }
result ← submit_kernel (fail)
failsubmit_kernel submit_kernel FAILED: kernel did not compile. Error building extension 'netvlad_post_ext_v1': [1/3] /opt/conda/bin/x86_64-conda-linux-gnu-c++ -MMD -MF main.o.d -DTORCH_EXTENSION_NAME=netvlad_post_ext_v1 -DTORCH_API_INCLUDE_EXTENSION_H -isystem /scratch/abaner/KernelBench/.venv/lib/python3.10/site-packages/torch/include -isystem /scratch/abaner/KernelBench/.venv/lib/python3.10/site-packages/torch/include/torch/csrc/api/include -isystem /usr/local/cuda/include -isystem /opt/conda/include/python3.10 -fPIC -std=c++17 -O3 -c /scratch/abaner/KernelBench/runs/l123_single_gpt/original/gpt-5.5-priority/level_3_problem_46_cache/k_35bde5580302/netvlad_post_ext_v1/main.cpp -o main.o [2/3] /usr/local/cuda/bin/nvcc -MD -MF cuda.cuda.o.d -ccbin /opt/conda/bin/x86_64-conda-linux-gnu-cc -DTORCH_EXTENSION_NAME=netvlad_post_ext_v1 -DTORCH_API_INCLUDE_EXTENSION_H -isystem /scratch/abaner/KernelBench/.venv/lib/python3.10/site-packages/torch/include -isystem /scratch/abaner/KernelBench/.venv/lib/python3.10/site-packages/torch/include/torch/csrc/api/include -isystem /usr/local/cuda/include -isystem /opt/conda/include/python3.10 -D__CUDA_NO_HALF_OPERATORS__ -D__CUDA_NO_HALF_CONVERSIONS__ -D__CUDA_NO_BFLOAT16_CONVERSIONS__ -D__CUDA_NO_HALF2_OPERATORS__ --expt-relaxed-constexpr -gencode=arch=compute_90,code=compute_90 -gencode=arch=compute_90,code=sm_90 --compiler-options '-fPIC' -O3 -std=c++17 -c /scratch/abaner/KernelBench/runs/l123_single_gpt/original/gpt-5.5-priority/level_3_problem_46_cache/k_35bde5580302/netvlad_post_ext_v1/cuda.cu -o cuda.cuda.o FAILED: [code=2] cuda.cuda.o /usr/local/cuda/bin/nvcc -MD -MF cuda.cuda.o.d -ccbin /opt/conda/bin/x86_64-conda-linux-gnu-cc -DTORCH_EXTENSION_NAME=netvlad_post_ext_v1 -DTORCH_API_INCLUDE_EXTENSION_H -isystem /scratch/abaner/KernelBench/.venv/lib/python3.10/site-packages/torch/include -isystem /scratch/abaner/KernelBench/.venv/lib/python3.10/site-packages/torch/include/torch/csrc/api/include -isystem /usr/local/cuda/include -isystem /opt/conda/include/python3.10 -D__CUDA_NO_HALF_OPERATORS__ -D__CUDA_NO_HALF_CONVERSIONS__ -D__CUDA_NO_BFLOAT16_CONVERSIONS__ -D__CUDA_NO_HALF2_OPERATORS__ --expt-relaxed-constexpr -gencode=arch=compute_90,code=compute_90 -gencode=arch=compute_90,code=sm_90 --compiler-options '-fPIC' -O3 -std=c++17 -c /scratch/abaner/KernelBench/runs/l123_single_gpt/original/gpt-5.5-priority/level_3_problem_46_cache/k_35bde5580302/netvlad_post_ext_v1/cuda.cu -o cuda.cuda.o nvcc warning : incompatible redefinition for option 'compiler-bindir', the last value of this option was used /scratch/abaner/KernelBench/runs/l123_single_gpt/original/gpt-5.5-priority/level_3_problem_46_cache/k_35bde5580302/netvlad_post_ext_v1/cuda.cu(22): error: identifier "CUDART_INF_F" is undefined float mx = -CUDART_INF_F; ^ 1 error detected in the compilation of "/scratch/abaner/KernelBench/runs/l123_single_gpt/original/gpt-5.5-priority/level_3_problem_46_cache/k_35bde5580302/netvlad_post_ext_v1/cuda.cu". ninja: build stopped: subcommand failed.
final submitted kernel (197 lines, 6,825 chars) — full source, untruncated
import math
import torch
import torch.nn as nn
import torch.nn.functional as F
from torch.utils.cpp_extension import load_inline


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

torch::Tensor post_forward(torch::Tensor x, torch::Tensor assignment, torch::Tensor clusters2, int64_t cluster_size);
"""

_cuda_src = r"""
#include <torch/extension.h>
#include <ATen/cuda/CUDAContext.h>
#include <c10/cuda/CUDAGuard.h>
#include <c10/cuda/CUDAException.h>
#include <cuda.h>
#include <cuda_runtime.h>
#include <math.h>

__global__ void softmax_store_kernel(const float* __restrict__ a,
                                     float* __restrict__ p,
                                     float* __restrict__ asum,
                                     int rows, int C, int K, int N) {
    int row = blockIdx.x;
    if (row >= rows) return;
    int tid = threadIdx.x;
    extern __shared__ float smem[];

    float mx = -CUDART_INF_F;
    const float* ar = a + (long long)row * C;
    for (int c = tid; c < C; c += blockDim.x) {
        float v = ar[c];
        mx = fmaxf(mx, v);
    }
    smem[tid] = mx;
    __syncthreads();
    for (int s = blockDim.x >> 1; s > 0; s >>= 1) {
        if (tid < s) smem[tid] = fmaxf(smem[tid], smem[tid + s]);
        __syncthreads();
    }
    mx = smem[0];

    float sum = 0.0f;
    for (int c = tid; c < C; c += blockDim.x) {
        sum += expf(ar[c] - mx);
    }
    smem[tid] = sum;
    __syncthreads();
    for (int s = blockDim.x >> 1; s > 0; s >>= 1) {
        if (tid < s) smem[tid] += smem[tid + s];
        __syncthreads();
    }
    float inv = 1.0f / smem[0];

    int b = row / N;
    int n = row - b * N;
    float* pr = p + ((long long)b * N + n) * K;
    float* as = asum + (long long)b * K;
    for (int k = tid; k < K; k += blockDim.x) {
        float pv = expf(ar[k] - mx) * inv;
        pr[k] = pv;
        atomicAdd(as + k, pv);
    }
}

__global__ void vlad_bmm_kernel(const float* __restrict__ x,
                                const float* __restrict__ p,
                                const float* __restrict__ asum,
                                const float* __restrict__ c2,
                                float* __restrict__ tmp,
                                int B, int N, int D, int K) {
    int d = blockIdx.x * 16 + threadIdx.x;
    int k = blockIdx.y * 16 + threadIdx.y;
    int b = blockIdx.z;
    if (d >= D || k >= K || b >= B) return;

    float acc = 0.0f;
    const float* xb = x + (long long)b * N * D + d;
    const float* pb = p + (long long)b * N * K + k;
    #pragma unroll 4
    for (int n = 0; n < N; ++n) {
        acc += pb[(long long)n * K] * xb[(long long)n * D];
    }
    acc -= asum[(long long)b * K + k] * c2[(long long)d * K + k];
    tmp[((long long)b * K + k) * D + d] = acc;
}

__global__ void norm_output_kernel(const float* __restrict__ tmp,
                                   float* __restrict__ out,
                                   int B, int D, int K) {
    int b = blockIdx.x;
    int k = blockIdx.y;
    int tid = threadIdx.x;
    extern __shared__ float smem[];

    float ss = 0.0f;
    const float* base = tmp + ((long long)b * K + k) * D;
    for (int d = tid; d < D; d += blockDim.x) {
        float v = base[d];
        ss += v * v;
    }
    smem[tid] = ss;
    __syncthreads();
    for (int s = blockDim.x >> 1; s > 0; s >>= 1) {
        if (tid < s) smem[tid] += smem[tid + s];
        __syncthreads();
    }
    float norm = sqrtf(smem[0]);
    float scale = (norm > 1.0e-12f ? 1.0f / norm : 1.0e12f) * rsqrtf((float)K);
    float* ob = out + (long long)b * D * K + k;
    for (int d = tid; d < D; d += blockDim.x) {
        ob[(long long)d * K] = base[d] * scale;
    }
}

torch::Tensor post_forward(torch::Tensor x, torch::Tensor assignment, torch::Tensor clusters2, int64_t cluster_size) {
    TORCH_CHECK(x.is_cuda(), "x must be CUDA");
    TORCH_CHECK(assignment.is_cuda(), "assignment must be CUDA");
    TORCH_CHECK(clusters2.is_cuda(), "clusters2 must be CUDA");
    TORCH_CHECK(x.scalar_type() == torch::kFloat32, "only float32 supported");
    TORCH_CHECK(assignment.scalar_type() == torch::kFloat32, "only float32 supported");
    c10::cuda::CUDAGuard guard(x.device());

    int B = (int)x.size(0);
    int N = (int)x.size(1);
    int D = (int)x.size(2);
    int C = (int)assignment.size(1);
    int K = (int)cluster_size;
    int rows = B * N;

    auto opts = x.options();
    auto probs = torch::empty({B, N, K}, opts);
    auto asum = torch::zeros({B, K}, opts);

    cudaStream_t stream = at::cuda::getCurrentCUDAStream();
    int sm_threads = 64;
    softmax_store_kernel<<<rows, sm_threads, sm_threads * sizeof(float), stream>>>(
        assignment.data_ptr<float>(), probs.data_ptr<float>(), asum.data_ptr<float>(), rows, C, K, N);

    auto tmp = torch::empty({B, K, D}, opts);
    dim3 block(16, 16, 1);
    dim3 grid((D + 15) / 16, (K + 15) / 16, B);
    vlad_bmm_kernel<<<grid, block, 0, stream>>>(
        x.data_ptr<float>(), probs.data_ptr<float>(), asum.data_ptr<float>(),
        clusters2.data_ptr<float>(), tmp.data_ptr<float>(), B, N, D, K);

    auto out = torch::empty({B, D * K}, opts);
    dim3 grid2(B, K, 1);
    int nt = 256;
    norm_output_kernel<<<grid2, nt, nt * sizeof(float), stream>>>(
        tmp.data_ptr<float>(), out.data_ptr<float>(), B, D, K);
    C10_CUDA_KERNEL_LAUNCH_CHECK();
    return out;
}
"""

_ext = load_inline(
    name="netvlad_post_ext_v1",
    cpp_sources=_cpp_src,
    cuda_sources=_cuda_src,
    functions=["post_forward"],
    extra_cflags=["-O3"],
    extra_cuda_cflags=["-O3"],
    verbose=False,
)


class ModelNew(nn.Module):
    def __init__(self, cluster_size, feature_size, ghost_clusters):
        super(ModelNew, self).__init__()
        self.feature_size = feature_size
        self.cluster_size = cluster_size
        self.ghost_clusters = ghost_clusters

        init_sc = (1 / math.sqrt(feature_size))
        clusters = cluster_size + ghost_clusters
        self.clusters = nn.Parameter(init_sc * torch.randn(feature_size, clusters))
        self.batch_norm = nn.BatchNorm1d(clusters)
        self.clusters2 = nn.Parameter(init_sc * torch.randn(1, feature_size, cluster_size))
        self.out_dim = self.cluster_size * feature_size

    def forward(self, x, mask=None):
        max_sample = x.size(1)
        x_contig = x.contiguous()
        x_flat = x_contig.view(-1, self.feature_size)
        if x_flat.device != self.clusters.device:
            msg = f"x.device {x_flat.device} != cluster.device {self.clusters.device}"
            raise ValueError(msg)
        assignment = torch.matmul(x_flat, self.clusters)
        assignment = self.batch_norm(assignment)
        assignment = assignment.contiguous()
        c2 = self.clusters2.contiguous()
        return _ext.post_forward(x_contig, assignment, c2, self.cluster_size)