Kimi-K2.6 · original · l12_default_gpt
correct 21_Sigmoid.py
model
Kimi-K2.6
turns / max
5 / 10
tool calls
5
tokens (total)
0
wall clock
—
truncated
no
backend
cuda fp32
variant
original
runtime
4.66 μs
ref runtime
4.30 μs
speedup
0.92x
started
2026-05-02T03:44:25Z
timingkernelbench.timing
kernel runtime
4.66 μs
ref runtime
4.30 μs
speedup
0.92x
kernel mean / median / std
4.66 / — / 0.68 μs
ref mean / median / std
4.30 / — / 0.09 μs
numerical precisioneval_kernel_against_ref
max abs error
1.19e-07
mean abs error
1.51e-08
max rel error
2.38e-07
mean rel error
2.42e-08
gpu memoryextended_metrics.measure_memory
kernel peak
12320.00 MB
ref peak
12320.00 MB
kernel / ref
1.00x
kernel launches / fusiontorch.profiler
kernel launches
2
ref launches
1
fusion ratio (ref / kernel)
0.50x
total cuda time
68 μs
energyNVML (pynvml)
kernel mJ / run
774.84
ref mJ / run
516.78
ratio (ref / kernel)
0.67x
avg power
168.58 W
ref avg power
118.90 W
SOL (speed-of-light)nsight_hardware_counters
SOL score (0–1)
0.852
dram util %
85.2%
compute util %
0.0%
bottleneck
unknown
dominant pipe
None
arithmetic intensity
—
ridge point
20.00
roofline / occupancynsight_hardware_counters
dram bandwidth
2855.21 GB/s
dram util %
85.2%
fp32 tflops
—
fp32 util %
—
fp16 tflops
—
occupancy %
94.9%
bottleneck
unknown
dominant pipe %
—
arithmetic intensity
—
ridge point
20.00
L1 hit %
—
L2 hit %
50.9%
ld sectors / req
16.00
st sectors / req
—
regs / thread
32
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
17438.7%
mio_throttle
112.0%
short_scoreboard
51.9%
reference PyTorch source (the prompt the agent sees)
import torch
import torch.nn as nn

class Model(nn.Module):
    """
    Simple model that performs a Sigmoid activation.
    """
    def __init__(self):
        super(Model, self).__init__()
    
    def forward(self, x: torch.Tensor) -> torch.Tensor:
        """
        Applies Sigmoid activation to the input tensor.

        Args:
            x (torch.Tensor): Input tensor of any shape.

        Returns:
            torch.Tensor: Output tensor with Sigmoid applied, same shape as input.
        """
        return torch.sigmoid(x)

batch_size = 4096
dim = 393216

def get_inputs():
    x = torch.rand(batch_size, dim)
    return [x]

def get_init_inputs():
    return []  # No special initialization inputs needed
turn 05.1s turn · 5.1s LLM · 0.0s tools
reasoning
We need to write a custom CUDA kernel implementing sigmoid for large 2D tensors (batch_size=4096, dim=393216). The idea is to write a simple elementwise CUDA kernel that computes 1/(1+exp(-x)). Need to ensure numerical equivalence (fp32) within tolerance 1e-4. PyTorch's sigmoid uses a numerically stable formula. We can simply compute 1 / (1 + expf(-x)). For large negative x, exp(-x) overflows to inf, but 1/(1+inf)=0. That's fine. For large positive x, exp(-x)=0, 1/(1+0)=1. That's fine. But to be safe, we might implement the same piecewise logic as PyTorch: for x >= 0, z = exp(-x); return 1/(1+z); else z = exp(x); return z/(1+z). This avoids overflow for large negative x? Actually exp(-x) for x large negative overflows, because -x is large positive. Using the piecewise avoids that: for x<0 compute exp(x) which is small. For x>=0 compute exp(-x) which is small. So we should implement that piecewise stable version to match PyTorch exactly. We need to load the CUDA kernel using torch.utils.cpp_extension.load_inline. Need to be careful about performance: tensor is large (4096*393216 = 1,610,612,736 elements, ~1.6B, ~6.4 GB in fp32). That's huge. Memory bandwidth bound. We need a kernel that reads input and writes output. The GPU probably has high mem bandwidth (e.g., A100 1.5-2 TB/s). So we want to saturate memory bandwidth with a simple kernel. Potential issues: kernel launch on 1.6B elements might need many blocks. We can use grid-stride loops or use enough blocks. Since each thread can process multiple elements to amortize overhead, we can use a grid-stride loop: for (int i = blockIdx.x * blockDim.x + threadIdx.x; i < n; i += blockDim.x * gridDim.x) { ... }. We need to know GPU specs to decide block size and occupancy. Let's call get_gpu_specs.
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.2s turn · 13.2s LLM · 7,835 chars (click to expand)
turn 113.2s turn · 13.2s LLM · 0.0s tools
reasoning
Now I need to write a custom CUDA kernel for sigmoid activation. Let me analyze the problem: 1. Input shape: batch_size=4096, dim=393216 2. Total elements: 4096 * 393216 = 1,610,612,736 ≈ 1.61B elements 3. At fp32, that's about 6.44 GB of data 4. Sigmoid is bandwidth-bound: read x, compute exp, write output The operation is: sigmoid(x) = 1 / (1 + exp(-x)) For an H100 with 3.35 TB/s bandwidth: - Memory bandwidth bound kernel: 6.44 GB read + 6.44 GB write = ~12.88 GB total memory traffic - Minimum time = 12.88 GB / 3.35 TB/s ≈ 3.84 ms But actually, sigmoid reads input and writes output, so 2 * 6.44 GB = 12.88 GB total. Wait, in practice we might only count one read plus one write for non-coalesced or coalesced cases. For such a large tensor, a simple elementwise kernel should be close to bandwidth-bound. Let me write an efficient vectorized kernel. For large contiguous arrays, I should use vectorized loads/stores (float4) and enough threads. Let me write the kernel code: ```python import torch from torch.utils.cpp_extension import load_inline # Let me think about the optimal kernel # Total elements: 4096 * 393216 = 1,610,612,736 # This is divisible by many powers of 2 # 393216 = 2^17 * 3 = 131072 * 3 # 4096 = 2^12 # Total = 2^29 * 3 # # A vectorized kernel using float4 would process 4 elements per thread # Total threads needed = 1,610,612,736 / 4 = 402,653,184 # With 256 threads per block: 1,572,864 blocks # With 128 threads per block: 3,145,728 blocks # # Actually, I should check if we can use float4, float2, etc. ``` Let me write a simple and efficient kernel. I'll use float4 vectorization if possible. Actually, for the H100, a very simple kernel with enough threads should be close to optimal. Let me use a grid-stride loop with vectorized memory accesses. ```python import torch from torch.utils.cpp_extension import load_inline cuda_source = ''' #include <torch/extension.h> #include <cuda_runtime.h> #include <cmath> __global__ void sigmoid_kernel(const float* __restrict__ input, float* __restrict__ output, int64_t n) { int64_t idx = blockIdx.x * blockDim.x + threadIdx.x; int64_t stride = blockDim.x * gridDim.x; for (int64_t i = idx; i < n; i += stride) { float x = input[i]; output[i] = 1.0f / (1.0f + expf(-x)); } } torch::Tensor sigmoid_cuda(torch::Tensor input) { auto output = torch::empty_like(input); int64_t n = input.numel(); const int threads = 256; const int blocks = (n + threads - 1) / threads; // Cap blocks to avoid launching too many const int max_blocks = 65535 * 256; // large enough const int actual_blocks = min(blocks, max_blocks); sigmoid_kernel<<<actual_blocks, threads>>>(input.data_ptr<float>(), output.data_ptr<float>(), n); return output; } ''' cpp_source = "torch::Tensor sigmoid_cuda(torch::Tensor input);" ``` Wait, but we can do better with vectorization. Let me write a float4 version: Actually, let me first try a simple version and see how it performs. But I also want to consider using `__ldg` for read-only cached loads and vectorized memory access. For H100, memory bandwidth is the key. Let me try using float4: ```cuda __global__ void sigmoid_kernel_float4(const float* __restrict__ input, float* __restrict__ output, int64_t n) { int64_t idx = (blockIdx.x * blockDim.x + threadIdx.x) * 4; int64_t stride = blockDim.x * gridDim.x * 4; for (int64_t i = idx; i < n; i += stride) { if (i + 3 < n) { float4 x = reinterpret_cast<const float4*>(input)[i / 4]; x.x = 1.0f / (1.0f + expf(-x.x)); x.y = 1.0f / (1.0f + expf(-x.y)); x.z = 1.0f / (1.0f + expf(-x.z)); x.w = 1.0f / (1.0f + expf(-x.w)); reinterpret_cast<float4*>(output)[i / 4] = x; } else { for (int64_t j = i; j < n; ++j) { output[j] = 1.0f / (1.0f + expf(-input[j])); } } } } ``` Actually, I need to be careful about alignment for float4. The tensor might be aligned, but I should use a scalar tail loop. Let me write a cleaner version. For 1.61B elements, the tail won't matter much. Let me also consider using a larger block size and more blocks. On H100, we want to saturate all SMs. Actually, let me think about what PyTorch's native sigmoid does. PyTorch likely uses its elementwise kernel system which is already quite optimized. But we might be able to beat it by: 1. Reducing launch overhead 2. Better occupancy 3. Vectorized loads Actually, PyTorch uses ATen's `sigmoid_kernel` which likely uses TensorIterator and is quite efficient. But a custom kernel that does a single monolithic launch might have less overhead, especially for huge tensors. Let me try a float4 vectorized kernel. One issue: `393216 = 3 * 131072`, so `4096 * 393216 = 12288 * 131072 = 1610612736`. This is divisible by 4 (and much more), so float4 should work well. Let me write the kernel.
call → compile_kernel
callcompile_kernel { "kernel_code": "import torch\nfrom torch.utils.cpp_extension import load_inline\n\ncuda_source = '''\n#include <torch/extension.h>\n#include <cuda_runtime.h>\n\n__global__ void sigmoid_kernel_float4(const float* __restrict__ input, float* __restrict__ output, int64_t n) {\n int64_t idx = (blockIdx.x * blockDim.x + threadIdx.x);\n int64_t num_vec = n / 4;\n int64_t stride = blockDim.x * gridDim.x;\n \n // Process 4 elements at a time using float4\n for (int64_t i = idx; i < num_vec; i += stride) {\n float4 x = reinterpret_cast<const float4*>(input)[i];\n x.x = 1.0f / (1.0f + expf(-x.x));\n x.y = 1.0f / (1.0f + expf(-x.y));\n x.z = 1.0f / (1.0f + expf(-x.z));\n x.w = 1.0f / (1.0f + expf(-x.w));\n reinterpret_cast<float4*>(output)[i] = x;\n }\n \n // Handle remaining elements\n int64_t remainder_start = num_vec * 4;\n for (int64_t i = remainder_start + idx; i < n; i += stride) {\n float x = input[i];\n output[i] = 1.0f / (1.0f + expf(-x));\n }\n}\n\ntorch::Tensor sigmoid_cuda(torch::Tensor input) {\n auto output = torch::empty_like(input);\n int64_t n = input.numel();\n \n const int threads = 256;\n const int blocks = 8192; // Enough to saturate H100\n \n sigmoid_kernel_float4<<<blocks, threads>>>(input.data_ptr<float>(), output.data_ptr<float>(), n);\n return output;\n}\n'''\n\ncpp_source = '''\ntorch::Tensor sigmoid_cuda(torch::Tensor input);\n'''\n\n# Compile the extension\nsigmoid_ext = load_inline(\n name=\"sigmoid_ext\",\n cpp_sources=[cpp_source],\n cuda_sources=[cuda_source],\n functions=[\"sigmoid_cuda\"],\n extra_cuda_cflags=[\"-O3\", \"--use_fast_math\"],\n verbose=False\n)\n\nclass ModelNew(torch.nn.Module):\n def __init__(self):\n super(ModelNew, self).__init__()\n \n def forward(self, x: torch.Tensor) -> torch.Tensor:\n return sigmoid_ext.sigmoid_cuda(x)\n" }
result ← compile_kernel (ok)
okcompile_kernel compile_kernel PASSED: kernel compiled without errors.
turn 2200.9s turn · 200.9s LLM · 0.0s tools
reasoning
Good, it compiled. Now let's check correctness.
call → run_correctness
callrun_correctness { "kernel_code": "import torch\nfrom torch.utils.cpp_extension import load_inline\n\ncuda_source = '''\n#include <torch/extension.h>\n#include <cuda_runtime.h>\n\n__global__ void sigmoid_kernel_float4(const float* __restrict__ input, float* __restrict__ output, int64_t n) {\n int64_t idx = (blockIdx.x * blockDim.x + threadIdx.x);\n int64_t num_vec = n / 4;\n int64_t stride = blockDim.x * gridDim.x;\n \n // Process 4 elements at a time using float4\n for (int64_t i = idx; i < num_vec; i += stride) {\n float4 x = reinterpret_cast<const float4*>(input)[i];\n x.x = 1.0f / (1.0f + expf(-x.x));\n x.y = 1.0f / (1.0f + expf(-x.y));\n x.z = 1.0f / (1.0f + expf(-x.z));\n x.w = 1.0f / (1.0f + expf(-x.w));\n reinterpret_cast<float4*>(output)[i] = x;\n }\n \n // Handle remaining elements\n int64_t remainder_start = num_vec * 4;\n for (int64_t i = remainder_start + idx; i < n; i += stride) {\n float x = input[i];\n output[i] = 1.0f / (1.0f + expf(-x));\n }\n}\n\ntorch::Tensor sigmoid_cuda(torch::Tensor input) {\n auto output = torch::empty_like(input);\n int64_t n = input.numel();\n \n const int threads = 256;\n const int blocks = 8192; // Enough to saturate H100\n \n sigmoid_kernel_float4<<<blocks, threads>>>(input.data_ptr<float>(), output.data_ptr<float>(), n);\n return output;\n}\n'''\n\ncpp_source = '''\ntorch::Tensor sigmoid_cuda(torch::Tensor input);\n'''\n\n# Compile the extension\nsigmoid_ext = load_inline(\n name=\"sigmoid_ext\",\n cpp_sources=[cpp_source],\n cuda_sources=[cuda_source],\n functions=[\"sigmoid_cuda\"],\n extra_cuda_cflags=[\"-O3\", \"--use_fast_math\"],\n verbose=False\n)\n\nclass ModelNew(torch.nn.Module):\n def __init__(self):\n super(ModelNew, self).__init__()\n \n def forward(self, x: torch.Tensor) -> torch.Tensor:\n return sigmoid_ext.sigmoid_cuda(x)\n" }
result ← run_correctness (ok)
okrun_correctness run_correctness PASSED: (5 / 5) trials all matched the reference. Numerical precision: max_abs_err=1.19e-07 mean_abs_err=1.51e-08 max_rel_err=2.38e-07
turn 3130.8s turn · 130.8s LLM · 0.0s tools
reasoning
The correctness test passed. Now let me do the static check and then submit.
call → static_check
callstatic_check { "kernel_code": "import torch\nfrom torch.utils.cpp_extension import load_inline\n\ncuda_source = '''\n#include <torch/extension.h>\n#include <cuda_runtime.h>\n\n__global__ void sigmoid_kernel_float4(const float* __restrict__ input, float* __restrict__ output, int64_t n) {\n int64_t idx = (blockIdx.x * blockDim.x + threadIdx.x);\n int64_t num_vec = n / 4;\n int64_t stride = blockDim.x * gridDim.x;\n \n // Process 4 elements at a time using float4\n for (int64_t i = idx; i < num_vec; i += stride) {\n float4 x = reinterpret_cast<const float4*>(input)[i];\n x.x = 1.0f / (1.0f + expf(-x.x));\n x.y = 1.0f / (1.0f + expf(-x.y));\n x.z = 1.0f / (1.0f + expf(-x.z));\n x.w = 1.0f / (1.0f + expf(-x.w));\n reinterpret_cast<float4*>(output)[i] = x;\n }\n \n // Handle remaining elements\n int64_t remainder_start = num_vec * 4;\n for (int64_t i = remainder_start + idx; i < n; i += stride) {\n float x = input[i];\n output[i] = 1.0f / (1.0f + expf(-x));\n }\n}\n\ntorch::Tensor sigmoid_cuda(torch::Tensor input) {\n auto output = torch::empty_like(input);\n int64_t n = input.numel();\n \n const int threads = 256;\n const int blocks = 8192; // Enough to saturate H100\n \n sigmoid_kernel_float4<<<blocks, threads>>>(input.data_ptr<float>(), output.data_ptr<float>(), n);\n return output;\n}\n'''\n\ncpp_source = '''\ntorch::Tensor sigmoid_cuda(torch::Tensor input);\n'''\n\n# Compile the extension\nsigmoid_ext = load_inline(\n name=\"sigmoid_ext\",\n cpp_sources=[cpp_source],\n cuda_sources=[cuda_source],\n functions=[\"sigmoid_cuda\"],\n extra_cuda_cflags=[\"-O3\", \"--use_fast_math\"],\n verbose=False\n)\n\nclass ModelNew(torch.nn.Module):\n def __init__(self):\n super(ModelNew, self).__init__()\n \n def forward(self, x: torch.Tensor) -> torch.Tensor:\n return sigmoid_ext.sigmoid_cuda(x)\n" }
result ← static_check (ok)
okstatic_check static_check PASSED: no violations or warnings detected.
turn 443.0s turn · 43.0s LLM · 0.0s tools · FINAL
reasoning
The compilation, correctness, and static check all passed. The kernel is working correctly. Let me now submit it for evaluation. Wait - let me reconsider whether I can optimize this further. The current kernel is a simple memory-bound kernel using float4. For an H100, we might want to use more blocks or optimize occupancy better. Actually, let me look at what we have: - Input size: batch_size=4096, dim=393216 → total elements = 4096 × 393216 = 1,610,612,736 elements = ~6.44 GB - For H100 with 3.35 TB/s bandwidth and ~67 TFLOPS - This is memory-bound. The arithmetic intensity is very low (1 expf + 1 add + 1 div per float). - Memory bandwidth is the limiting factor. The PyTorch native sigmoid probably also uses a similar approach. The main question is whether our kernel is launching enough threads and whether there are any hidden overheads. Let me also try using more threads or a different block configuration. But first, let me just submit this since correctness passed and theoretically a simple custom kernel should match or beat PyTorch's dispatch overhead. PyTorch torch.sigmoid goes through ATen dispatch which has some overhead. Actually, let me try one more optimization: using grid-stride loops with large stride but also ensuring we launch enough SMs. Let me also try adding __launch_bounds__ to hint the compiler. Actually, I'll submit the current one and see what happens. But wait - maybe let me try with even more blocks or more threads per SM. Let me also consider if there's any benefit from using 512 threads per block or from increasing blocks even more. For this size (1.6B elements) and 8192 blocks × 256 threads = 2,097,152 threads, each thread processes about 768 elements. That's a lot of work per thread but with a large stride. Actually, let me try increasing to max blocks (65K or more). But there's no time to test so many configurations. Let me just submit what we have. Actually, I just realized I should try one more thing: instead of using 8192 blocks, let me use 65535 blocks to saturate the GPU better. The issue with fewer blocks is that some SMs might go idle. But then each thread processes fewer elements. The overhead of more thread blocks might help with latency hiding. Hmm, but I only have 30 total tool calls. I've already used 8. I should just submit the current one if I think it's good. Let me submit.
call → submit_kernel
callsubmit_kernel { "kernel_code": "import torch\nfrom torch.utils.cpp_extension import load_inline\n\ncuda_source = '''\n#include <torch/extension.h>\n#include <cuda_runtime.h>\n\n__global__ void sigmoid_kernel_float4(const float* __restrict__ input, float* __restrict__ output, int64_t n) {\n int64_t idx = (blockIdx.x * blockDim.x + threadIdx.x);\n int64_t num_vec = n / 4;\n int64_t stride = blockDim.x * gridDim.x;\n \n // Process 4 elements at a time using float4\n for (int64_t i = idx; i < num_vec; i += stride) {\n float4 x = reinterpret_cast<const float4*>(input)[i];\n x.x = 1.0f / (1.0f + expf(-x.x));\n x.y = 1.0f / (1.0f + expf(-x.y));\n x.z = 1.0f / (1.0f + expf(-x.z));\n x.w = 1.0f / (1.0f + expf(-x.w));\n reinterpret_cast<float4*>(output)[i] = x;\n }\n \n // Handle remaining elements\n int64_t remainder_start = num_vec * 4;\n for (int64_t i = remainder_start + idx; i < n; i += stride) {\n float x = input[i];\n output[i] = 1.0f / (1.0f + expf(-x));\n }\n}\n\ntorch::Tensor sigmoid_cuda(torch::Tensor input) {\n auto output = torch::empty_like(input);\n int64_t n = input.numel();\n \n const int threads = 256;\n const int blocks = 8192; // Enough to saturate H100\n \n sigmoid_kernel_float4<<<blocks, threads>>>(input.data_ptr<float>(), output.data_ptr<float>(), n);\n return output;\n}\n'''\n\ncpp_source = '''\ntorch::Tensor sigmoid_cuda(torch::Tensor input);\n'''\n\n# Compile the extension\nsigmoid_ext = load_inline(\n name=\"sigmoid_ext\",\n cpp_sources=[cpp_source],\n cuda_sources=[cuda_source],\n functions=[\"sigmoid_cuda\"],\n extra_cuda_cflags=[\"-O3\", \"--use_fast_math\"],\n verbose=False\n)\n\nclass ModelNew(torch.nn.Module):\n def __init__(self):\n super(ModelNew, self).__init__()\n \n def forward(self, x: torch.Tensor) -> torch.Tensor:\n return sigmoid_ext.sigmoid_cuda(x)\n" }
result ← submit_kernel (ok)
oksubmit_kernel submit_kernel PASSED: (5 / 5) correctness trials all passed. Kernel runtime: 4.66 μs Runtime stats: mean=4.66μs median=?μs std=0.68μs Numerical precision: max_abs_err=1.19e-07 mean_abs_err=1.51e-08 max_rel_err=2.38e-07 Memory: 12320.0 MB peak (ref: 12320.0 MB, ratio: 1.0x) Kernel launches: 2 (ref: 1, fusion ratio: 0.5) Energy: 774.84 mJ/run (ref: 516.78 mJ, ratio: 0.667x) SOL score: 0.8523
final submitted kernel (63 lines, 1,869 chars) — full source, untruncated
import torch
from torch.utils.cpp_extension import load_inline

cuda_source = '''
#include <torch/extension.h>
#include <cuda_runtime.h>

__global__ void sigmoid_kernel_float4(const float* __restrict__ input, float* __restrict__ output, int64_t n) {
    int64_t idx = (blockIdx.x * blockDim.x + threadIdx.x);
    int64_t num_vec = n / 4;
    int64_t stride = blockDim.x * gridDim.x;
    
    // Process 4 elements at a time using float4
    for (int64_t i = idx; i < num_vec; i += stride) {
        float4 x = reinterpret_cast<const float4*>(input)[i];
        x.x = 1.0f / (1.0f + expf(-x.x));
        x.y = 1.0f / (1.0f + expf(-x.y));
        x.z = 1.0f / (1.0f + expf(-x.z));
        x.w = 1.0f / (1.0f + expf(-x.w));
        reinterpret_cast<float4*>(output)[i] = x;
    }
    
    // Handle remaining elements
    int64_t remainder_start = num_vec * 4;
    for (int64_t i = remainder_start + idx; i < n; i += stride) {
        float x = input[i];
        output[i] = 1.0f / (1.0f + expf(-x));
    }
}

torch::Tensor sigmoid_cuda(torch::Tensor input) {
    auto output = torch::empty_like(input);
    int64_t n = input.numel();
    
    const int threads = 256;
    const int blocks = 8192;  // Enough to saturate H100
    
    sigmoid_kernel_float4<<<blocks, threads>>>(input.data_ptr<float>(), output.data_ptr<float>(), n);
    return output;
}
'''

cpp_source = '''
torch::Tensor sigmoid_cuda(torch::Tensor input);
'''

# Compile the extension
sigmoid_ext = load_inline(
    name="sigmoid_ext",
    cpp_sources=[cpp_source],
    cuda_sources=[cuda_source],
    functions=["sigmoid_cuda"],
    extra_cuda_cflags=["-O3", "--use_fast_math"],
    verbose=False
)

class ModelNew(torch.nn.Module):
    def __init__(self):
        super(ModelNew, self).__init__()
    
    def forward(self, x: torch.Tensor) -> torch.Tensor:
        return sigmoid_ext.sigmoid_cuda(x)