gpt-5.5-priority · original · l12_default_gpt
correct 12_Matmul_with_diagonal_matrices_.py
model
gpt-5.5-priority
turns / max
6 / 10
tool calls
6
tokens (total)
0
wall clock
—
truncated
no
backend
cuda fp32
variant
original
runtime
0.05 μs
ref runtime
0.06 μs
speedup
1.23x
started
2026-05-02T17:21:29Z
timingkernelbench.timing
kernel runtime
0.05 μs
ref runtime
0.06 μs
speedup
1.23x
kernel mean / median / std
0.05 / — / 6.03e-04 μs
ref mean / median / std
0.06 / — / 7.46e-04 μs
numerical precisioneval_kernel_against_ref
max abs error
0.00
mean abs error
0.00
max rel error
0.00
mean rel error
0.00
gpu memoryextended_metrics.measure_memory
kernel peak
161.02 MB
ref peak
161.02 MB
kernel / ref
1.00x
kernel launches / fusiontorch.profiler
kernel launches
2
ref launches
3
fusion ratio (ref / kernel)
1.50x
total cuda time
80 μs
SOL (speed-of-light)nsight_hardware_counters
SOL score (0–1)
0.779
dram util %
77.9%
compute util %
0.0%
bottleneck
unknown
dominant pipe
None
arithmetic intensity
—
ridge point
20.00
roofline / occupancynsight_hardware_counters
dram bandwidth
2609.78 GB/s
dram util %
77.9%
fp32 tflops
—
fp32 util %
—
fp16 tflops
—
occupancy %
84.9%
bottleneck
unknown
dominant pipe %
—
arithmetic intensity
—
ridge point
20.00
L1 hit %
—
L2 hit %
49.9%
ld sectors / req
13.00
st sectors / req
—
regs / thread
18
smem / block
1024 B
block size
256
peak bw
3350.00 GB/s
peak fp32 tflops
67.00
peak fp16 tflops
1979.00
warp stalls (top 5)nsight
long_scoreboard
26804.4%
mio_throttle
135.8%
short_scoreboard
73.1%
reference PyTorch source (the prompt the agent sees)
import torch
import torch.nn as nn

class Model(nn.Module):
    """
    Simple model that performs a matrix multiplication of a diagonal matrix with another matrix.
    C = diag(A) * B
    """
    def __init__(self):
        super(Model, self).__init__()
    
    def forward(self, A, B):
        """
        Performs the matrix multiplication.

        Args:
            A (torch.Tensor): A 1D tensor representing the diagonal of the diagonal matrix. Shape: (N,).
            B (torch.Tensor): A 2D tensor representing the second matrix. Shape: (N, M).

        Returns:
            torch.Tensor: The result of the matrix multiplication. Shape: (N, M).
        """
        # Logically equivalent to torch.diag(A) @ B 
        # more efficient as no need to materialize a full N×N matrix
        return A.unsqueeze(1) * B

M = 4096
N = 4096

def get_inputs():
    A = torch.rand(N)
    B = torch.rand(N, M)
    return [A, B]

def get_init_inputs():
    return []  # No special initialization inputs needed
turn 04.4s turn · 4.4s LLM · 0.0s tools
reasoning
**Optimizing code for scaling** I need to produce code that optimizes element-wise row scaling for a 4096x4096 fp32 matrix. I’m thinking of using PyTorch's A.unsqueeze() and B as a fast torch mul kernel reference. Maybe I could implement a custom solution with vectorized float4. I need to define ModelNew without arguments and utilize load_inline. Lastly, I should gather the specifications before proceeding. It feels a bit complex, but I’m committed to getting this right!
call → get_gpu_specs
callget_gpu_specs {}
result ← get_gpu_specs (ok)
okget_gpu_specs GPU specs for NVIDIA H100 80GB HBM3: total memory (runtime): 79.1 GB spec entry: H100 GPU Architecture: Hopper GPU Memory: 80GB Memory Bandwidth: 3.35 TB/s FP64 TFLOPS: 34 FP64 Tensor Core TFLOPS: 67 FP32 TFLOPS: 67 TF32 Tensor Core TFLOPS: 989 with sparsity BFLOAT16 Tensore Core TFLOPS: 1979 with sparsity FP16 Tensor Core TFLOPS: 1979 with sparsity FP8 Tensor Core TFLOPS: 3958 with sparsity INT8 Tensor Core TOPS: 3958 with sparsity Register File Size: 64K 32-bit registers per SM Maximum number of registers per thread: 255 Maximum number of thread blocks per SM: 32 Shared memory capacity per SM: 228 KB Maximum shared memory per thread block: 227 KB
turn 1 · 1 tool call(s) · 13.1s turn · 13.1s LLM · 6,628 chars (click to expand)
turn 113.1s turn · 13.1s LLM · 0.0s tools
call → compile_kernel
callcompile_kernel { "kernel_code": "import torch\nimport torch.nn as nn\nfrom torch.utils.cpp_extension import load_inline\n\ncpp_src = r\"\"\"\n#include <torch/extension.h>\ntorch::Tensor diag_mul_cuda(torch::Tensor A, torch::Tensor B);\n\"\"\"\n\ncuda_src = r\"\"\"\n#include <torch/extension.h>\n#include <cuda.h>\n#include <cuda_runtime.h>\n\n__global__ void row_scale_vec4_kernel(const float* __restrict__ A,\n const float4* __restrict__ B4,\n float4* __restrict__ C4,\n int M4) {\n int row = blockIdx.x;\n float a = A[row];\n int base = row * M4;\n for (int j = threadIdx.x; j < M4; j += blockDim.x) {\n float4 b = B4[base + j];\n b.x *= a; b.y *= a; b.z *= a; b.w *= a;\n C4[base + j] = b;\n }\n}\n\n__global__ void row_scale_scalar_kernel(const float* __restrict__ A,\n const float* __restrict__ B,\n float* __restrict__ C,\n int M) {\n int row = blockIdx.x;\n float a = A[row];\n int base = row * M;\n for (int col = threadIdx.x; col < M; col += blockDim.x) {\n C[base + col] = a * B[base + col];\n }\n}\n\ntorch::Tensor diag_mul_cuda(torch::Tensor A, torch::Tensor B) {\n auto C = torch::empty_like(B);\n const int64_t N64 = B.size(0);\n const int64_t M64 = B.size(1);\n const int N = (int)N64;\n const int M = (int)M64;\n const int threads = 256;\n cudaStream_t stream = at::cuda::getCurrentCUDAStream();\n if ((M & 3) == 0) {\n int M4 = M >> 2;\n row_scale_vec4_kernel<<<N, threads, 0, stream>>>(\n A.data_ptr<float>(),\n reinterpret_cast<const float4*>(B.data_ptr<float>()),\n reinterpret_cast<float4*>(C.data_ptr<float>()),\n M4);\n } else {\n row_scale_scalar_kernel<<<N, threads, 0, stream>>>(\n A.data_ptr<float>(), B.data_ptr<float>(), C.data_ptr<float>(), M);\n }\n return C;\n}\n\"\"\"\n\n_ext = load_inline(name=\"diag_mul_ext_v1\",\n cpp_sources=[cpp_src],\n cuda_sources=[cuda_src],\n functions=[\"diag_mul_cuda\"],\n extra_cflags=[\"-O3\"],\n extra_cuda_cflags=[\"-O3\", \"--use_fast_math\"],\n verbose=False)\n\nclass ModelNew(nn.Module):\n def __init__(self):\n super(ModelNew, self).__init__()\n def forward(self, A, B):\n return _ext.diag_mul_cuda(A, B)\n" }
result ← compile_kernel (fail)
failcompile_kernel compile_kernel FAILED: RuntimeError. RuntimeError: Error building extension 'diag_mul_ext_v1': [1/3] /opt/conda/bin/x86_64-conda-linux-gnu-c++ -MMD -MF main.o.d -DTORCH_EXTENSION_NAME=diag_mul_ext_v1 -DTORCH_API_INCLUDE_EXTENSION_H -isystem /scratch/tejas/PopcornBench/.venv/lib/python3.10/site-packages/torch/include -isystem /scratch/tejas/PopcornBench/.venv/lib/python3.10/site-packages/torch/include/torch/csrc/api/include -isystem /usr/local/cuda/include -isystem /home/prabhune/.local/share/uv/python/cpython-3.10-linux-x86_64-gnu/include/python3.10 -fPIC -std=c++17 -O3 -c /scratch/tejas/PopcornBench/runs/l12_default/original/gpt-5.5-priority/level_1_problem_12_cache/k_be326478034a/diag_mul_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=diag_mul_ext_v1 -DTORCH_API_INCLUDE_EXTENSION_H -isystem /scratch/tejas/PopcornBench/.venv/lib/python3.10/site-packages/torch/include -isystem /scratch/tejas/PopcornBench/.venv/lib/python3.10/site-packages/torch/include/torch/csrc/api/include -isystem /usr/local/cuda/include -isystem /home/prabhune/.local/share/uv/python/cpython-3.10-linux-x86_64-gnu/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 --use_fast_math -std=c++17 -c /scratch/tejas/PopcornBench/runs/l12_default/original/gpt-5.5-priority/level_1_problem_12_cache/k_be326478034a/diag_mul_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=diag_mul_ext_v1 -DTORCH_API_INCLUDE_EXTENSION_H -isystem /scratch/tejas/PopcornBench/.venv/lib/python3.10/site-packages/torch/include -isystem /scratch/tejas/PopcornBench/.venv/lib/python3.10/site-packages/torch/include/torch/csrc/api/include -isystem /usr/local/cuda/include -isystem /home/prabhune/.local/share/uv/python/cpython-3.10-linux-x86_64-gnu/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 --use_fast_math -std=c++17 -c /scratch/tejas/PopcornBench/runs/l12_default/original/gpt-5.5-priority/level_1_problem_12_cache/k_be326478034a/diag_mul_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/tejas/PopcornBench/runs/l12_default/original/gpt-5.5-priority/level_1_problem_12_cache/k_be326478034a/diag_mul_ext_v1/cuda.cu(42): error: namespace "at::cuda" has no member "getCurrentCUDAStream" cudaStream_t stream = at::cuda::getCurrentCUDAStream(); ^ 1 error detected in the compilation of "/scratch/tejas/PopcornBench/runs/l12_default/original/gpt-5.5-priority/level_1_problem_12_cache/k_be326478034a/diag_mul_ext_v1/cuda.cu". ninja: build stopped: subcommand failed.
turn 25.5s turn · 5.5s LLM · 0.0s tools
call → compile_kernel
callcompile_kernel { "kernel_code": "import torch\nimport torch.nn as nn\nfrom torch.utils.cpp_extension import load_inline\n\ncpp_src = r\"\"\"\n#include <torch/extension.h>\ntorch::Tensor diag_mul_cuda(torch::Tensor A, torch::Tensor B);\n\"\"\"\n\ncuda_src = r\"\"\"\n#include <torch/extension.h>\n#include <ATen/cuda/CUDAContext.h>\n#include <cuda.h>\n#include <cuda_runtime.h>\n\n__global__ void row_scale_vec4_kernel(const float* __restrict__ A,\n const float4* __restrict__ B4,\n float4* __restrict__ C4,\n int M4) {\n int row = blockIdx.x;\n float a = A[row];\n int base = row * M4;\n for (int j = threadIdx.x; j < M4; j += blockDim.x) {\n float4 b = B4[base + j];\n b.x *= a; b.y *= a; b.z *= a; b.w *= a;\n C4[base + j] = b;\n }\n}\n\n__global__ void row_scale_scalar_kernel(const float* __restrict__ A,\n const float* __restrict__ B,\n float* __restrict__ C,\n int M) {\n int row = blockIdx.x;\n float a = A[row];\n int base = row * M;\n for (int col = threadIdx.x; col < M; col += blockDim.x) {\n C[base + col] = a * B[base + col];\n }\n}\n\ntorch::Tensor diag_mul_cuda(torch::Tensor A, torch::Tensor B) {\n auto C = torch::empty_like(B);\n const int N = (int)B.size(0);\n const int M = (int)B.size(1);\n const int threads = 256;\n cudaStream_t stream = at::cuda::getCurrentCUDAStream();\n if ((M & 3) == 0) {\n int M4 = M >> 2;\n row_scale_vec4_kernel<<<N, threads, 0, stream>>>(\n A.data_ptr<float>(),\n reinterpret_cast<const float4*>(B.data_ptr<float>()),\n reinterpret_cast<float4*>(C.data_ptr<float>()),\n M4);\n } else {\n row_scale_scalar_kernel<<<N, threads, 0, stream>>>(\n A.data_ptr<float>(), B.data_ptr<float>(), C.data_ptr<float>(), M);\n }\n return C;\n}\n\"\"\"\n\n_ext = load_inline(name=\"diag_mul_ext_v2\",\n cpp_sources=[cpp_src],\n cuda_sources=[cuda_src],\n functions=[\"diag_mul_cuda\"],\n extra_cflags=[\"-O3\"],\n extra_cuda_cflags=[\"-O3\", \"--use_fast_math\"],\n verbose=False)\n\nclass ModelNew(nn.Module):\n def __init__(self):\n super(ModelNew, self).__init__()\n def forward(self, A, B):\n return _ext.diag_mul_cuda(A, B)\n" }
result ← compile_kernel (ok)
okcompile_kernel compile_kernel PASSED: kernel compiled without errors.
turn 38.1s turn · 8.1s LLM · 0.0s tools
call → run_correctness
callrun_correctness { "kernel_code": "import torch\nimport torch.nn as nn\nfrom torch.utils.cpp_extension import load_inline\n\ncpp_src = r\"\"\"\n#include <torch/extension.h>\ntorch::Tensor diag_mul_cuda(torch::Tensor A, torch::Tensor B);\n\"\"\"\n\ncuda_src = r\"\"\"\n#include <torch/extension.h>\n#include <ATen/cuda/CUDAContext.h>\n#include <cuda.h>\n#include <cuda_runtime.h>\n\n__global__ void row_scale_vec4_kernel(const float* __restrict__ A,\n const float4* __restrict__ B4,\n float4* __restrict__ C4,\n int M4) {\n int row = blockIdx.x;\n float a = A[row];\n int base = row * M4;\n for (int j = threadIdx.x; j < M4; j += blockDim.x) {\n float4 b = B4[base + j];\n b.x *= a; b.y *= a; b.z *= a; b.w *= a;\n C4[base + j] = b;\n }\n}\n\n__global__ void row_scale_scalar_kernel(const float* __restrict__ A,\n const float* __restrict__ B,\n float* __restrict__ C,\n int M) {\n int row = blockIdx.x;\n float a = A[row];\n int base = row * M;\n for (int col = threadIdx.x; col < M; col += blockDim.x) {\n C[base + col] = a * B[base + col];\n }\n}\n\ntorch::Tensor diag_mul_cuda(torch::Tensor A, torch::Tensor B) {\n auto C = torch::empty_like(B);\n const int N = (int)B.size(0);\n const int M = (int)B.size(1);\n const int threads = 256;\n cudaStream_t stream = at::cuda::getCurrentCUDAStream();\n if ((M & 3) == 0) {\n int M4 = M >> 2;\n row_scale_vec4_kernel<<<N, threads, 0, stream>>>(\n A.data_ptr<float>(),\n reinterpret_cast<const float4*>(B.data_ptr<float>()),\n reinterpret_cast<float4*>(C.data_ptr<float>()),\n M4);\n } else {\n row_scale_scalar_kernel<<<N, threads, 0, stream>>>(\n A.data_ptr<float>(), B.data_ptr<float>(), C.data_ptr<float>(), M);\n }\n return C;\n}\n\"\"\"\n\n_ext = load_inline(name=\"diag_mul_ext_v2c\",\n cpp_sources=[cpp_src],\n cuda_sources=[cuda_src],\n functions=[\"diag_mul_cuda\"],\n extra_cflags=[\"-O3\"],\n extra_cuda_cflags=[\"-O3\", \"--use_fast_math\"],\n verbose=False)\n\nclass ModelNew(nn.Module):\n def __init__(self):\n super(ModelNew, self).__init__()\n def forward(self, A, B):\n return _ext.diag_mul_cuda(A, B)\n" }
result ← run_correctness (ok)
okrun_correctness run_correctness PASSED: (5 / 5) trials all matched the reference. Numerical precision: max_abs_err=0.00e+00 mean_abs_err=0.00e+00 max_rel_err=0.00e+00
turn 418.6s turn · 18.6s LLM · 0.0s tools
reasoning
**Considering name collisions** I'm thinking there might be an issue with name collisions when I compile my evaluator across different attempts. To avoid confusion, I should use unique names for each instance. The final name seems okay, but it’s probably a good idea to do a static check before I submit it just to be safe. I want to make sure everything is clear and won’t collide with anything else.
call → static_check
callstatic_check { "kernel_code": "import torch\nimport torch.nn as nn\nfrom torch.utils.cpp_extension import load_inline\n\ncpp_src = r\"\"\"\n#include <torch/extension.h>\ntorch::Tensor diag_mul_cuda(torch::Tensor A, torch::Tensor B);\n\"\"\"\n\ncuda_src = r\"\"\"\n#include <torch/extension.h>\n#include <ATen/cuda/CUDAContext.h>\n#include <cuda.h>\n#include <cuda_runtime.h>\n\n__global__ void row_scale_vec4_kernel(const float* __restrict__ A,\n const float4* __restrict__ B4,\n float4* __restrict__ C4,\n int M4) {\n int row = blockIdx.x;\n float a = A[row];\n int base = row * M4;\n for (int j = threadIdx.x; j < M4; j += blockDim.x) {\n float4 b = B4[base + j];\n b.x *= a; b.y *= a; b.z *= a; b.w *= a;\n C4[base + j] = b;\n }\n}\n\n__global__ void row_scale_scalar_kernel(const float* __restrict__ A,\n const float* __restrict__ B,\n float* __restrict__ C,\n int M) {\n int row = blockIdx.x;\n float a = A[row];\n int base = row * M;\n for (int col = threadIdx.x; col < M; col += blockDim.x) {\n C[base + col] = a * B[base + col];\n }\n}\n\ntorch::Tensor diag_mul_cuda(torch::Tensor A, torch::Tensor B) {\n auto C = torch::empty_like(B);\n const int N = (int)B.size(0);\n const int M = (int)B.size(1);\n const int threads = 256;\n cudaStream_t stream = at::cuda::getCurrentCUDAStream();\n if ((M & 3) == 0) {\n int M4 = M >> 2;\n row_scale_vec4_kernel<<<N, threads, 0, stream>>>(\n A.data_ptr<float>(),\n reinterpret_cast<const float4*>(B.data_ptr<float>()),\n reinterpret_cast<float4*>(C.data_ptr<float>()),\n M4);\n } else {\n row_scale_scalar_kernel<<<N, threads, 0, stream>>>(\n A.data_ptr<float>(), B.data_ptr<float>(), C.data_ptr<float>(), M);\n }\n return C;\n}\n\"\"\"\n\n_ext = load_inline(name=\"diag_mul_ext_final\",\n cpp_sources=[cpp_src],\n cuda_sources=[cuda_src],\n functions=[\"diag_mul_cuda\"],\n extra_cflags=[\"-O3\"],\n extra_cuda_cflags=[\"-O3\", \"--use_fast_math\"],\n verbose=False)\n\nclass ModelNew(nn.Module):\n def __init__(self):\n super(ModelNew, self).__init__()\n def forward(self, A, B):\n return _ext.diag_mul_cuda(A, B)\n" }
result ← static_check (ok)
okstatic_check static_check PASSED: no violations or warnings detected.
turn 520.2s turn · 20.2s LLM · 0.0s tools · FINAL
call → submit_kernel
callsubmit_kernel { "kernel_code": "import torch\nimport torch.nn as nn\nfrom torch.utils.cpp_extension import load_inline\n\ncpp_src = r\"\"\"\n#include <torch/extension.h>\ntorch::Tensor diag_mul_cuda(torch::Tensor A, torch::Tensor B);\n\"\"\"\n\ncuda_src = r\"\"\"\n#include <torch/extension.h>\n#include <ATen/cuda/CUDAContext.h>\n#include <cuda.h>\n#include <cuda_runtime.h>\n\n__global__ void row_scale_vec4_kernel(const float* __restrict__ A,\n const float4* __restrict__ B4,\n float4* __restrict__ C4,\n int M4) {\n int row = blockIdx.x;\n float a = A[row];\n int base = row * M4;\n for (int j = threadIdx.x; j < M4; j += blockDim.x) {\n float4 b = B4[base + j];\n b.x *= a;\n b.y *= a;\n b.z *= a;\n b.w *= a;\n C4[base + j] = b;\n }\n}\n\n__global__ void row_scale_scalar_kernel(const float* __restrict__ A,\n const float* __restrict__ B,\n float* __restrict__ C,\n int M) {\n int row = blockIdx.x;\n float a = A[row];\n int base = row * M;\n for (int col = threadIdx.x; col < M; col += blockDim.x) {\n C[base + col] = a * B[base + col];\n }\n}\n\ntorch::Tensor diag_mul_cuda(torch::Tensor A, torch::Tensor B) {\n auto C = torch::empty_like(B);\n const int N = (int)B.size(0);\n const int M = (int)B.size(1);\n const int threads = 256;\n cudaStream_t stream = at::cuda::getCurrentCUDAStream();\n if ((M & 3) == 0) {\n int M4 = M >> 2;\n row_scale_vec4_kernel<<<N, threads, 0, stream>>>(\n A.data_ptr<float>(),\n reinterpret_cast<const float4*>(B.data_ptr<float>()),\n reinterpret_cast<float4*>(C.data_ptr<float>()),\n M4);\n } else {\n row_scale_scalar_kernel<<<N, threads, 0, stream>>>(\n A.data_ptr<float>(), B.data_ptr<float>(), C.data_ptr<float>(), M);\n }\n return C;\n}\n\"\"\"\n\n_ext = load_inline(name=\"diag_mul_ext_final\",\n cpp_sources=[cpp_src],\n cuda_sources=[cuda_src],\n functions=[\"diag_mul_cuda\"],\n extra_cflags=[\"-O3\"],\n extra_cuda_cflags=[\"-O3\", \"--use_fast_math\"],\n verbose=False)\n\nclass ModelNew(nn.Module):\n def __init__(self):\n super(ModelNew, self).__init__()\n\n def forward(self, A, B):\n return _ext.diag_mul_cuda(A, B)\n" }
result ← submit_kernel (ok)
oksubmit_kernel submit_kernel PASSED: (5 / 5) correctness trials all passed. Kernel runtime: 0.05 μs Runtime stats: mean=0.05μs median=?μs std=0.00μs Numerical precision: max_abs_err=0.00e+00 mean_abs_err=0.00e+00 max_rel_err=0.00e+00 Memory: 161.02 MB peak (ref: 161.02 MB, ratio: 1.0x) Kernel launches: 2 (ref: 3, fusion ratio: 1.5) SOL score: 0.779
final submitted kernel (80 lines, 2,477 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 diag_mul_cuda(torch::Tensor A, torch::Tensor B);
"""

cuda_src = r"""
#include <torch/extension.h>
#include <ATen/cuda/CUDAContext.h>
#include <cuda.h>
#include <cuda_runtime.h>

__global__ void row_scale_vec4_kernel(const float* __restrict__ A,
                                      const float4* __restrict__ B4,
                                      float4* __restrict__ C4,
                                      int M4) {
    int row = blockIdx.x;
    float a = A[row];
    int base = row * M4;
    for (int j = threadIdx.x; j < M4; j += blockDim.x) {
        float4 b = B4[base + j];
        b.x *= a;
        b.y *= a;
        b.z *= a;
        b.w *= a;
        C4[base + j] = b;
    }
}

__global__ void row_scale_scalar_kernel(const float* __restrict__ A,
                                        const float* __restrict__ B,
                                        float* __restrict__ C,
                                        int M) {
    int row = blockIdx.x;
    float a = A[row];
    int base = row * M;
    for (int col = threadIdx.x; col < M; col += blockDim.x) {
        C[base + col] = a * B[base + col];
    }
}

torch::Tensor diag_mul_cuda(torch::Tensor A, torch::Tensor B) {
    auto C = torch::empty_like(B);
    const int N = (int)B.size(0);
    const int M = (int)B.size(1);
    const int threads = 256;
    cudaStream_t stream = at::cuda::getCurrentCUDAStream();
    if ((M & 3) == 0) {
        int M4 = M >> 2;
        row_scale_vec4_kernel<<<N, threads, 0, stream>>>(
            A.data_ptr<float>(),
            reinterpret_cast<const float4*>(B.data_ptr<float>()),
            reinterpret_cast<float4*>(C.data_ptr<float>()),
            M4);
    } else {
        row_scale_scalar_kernel<<<N, threads, 0, stream>>>(
            A.data_ptr<float>(), B.data_ptr<float>(), C.data_ptr<float>(), M);
    }
    return C;
}
"""

_ext = load_inline(name="diag_mul_ext_final",
                   cpp_sources=[cpp_src],
                   cuda_sources=[cuda_src],
                   functions=["diag_mul_cuda"],
                   extra_cflags=["-O3"],
                   extra_cuda_cflags=["-O3", "--use_fast_math"],
                   verbose=False)

class ModelNew(nn.Module):
    def __init__(self):
        super(ModelNew, self).__init__()

    def forward(self, A, B):
        return _ext.diag_mul_cuda(A, B)