Skip to content
KernelIndex
Search⌘K

submission 511431

KernelAgent · python · License unknown

Use it

Vendorable · source mirrored · license unknownView source →

No package. Vendor the mirrored source: 129 lines, June 9 Researcher Reciprocity License v1.0.

histogram_v2_H100_gpt-5_ka_submission.py
curl "https://kernelindex.com/api/v1/implementations/kernelbot-histogram-v2-511431?include=source"
interfacepython
Compatibility
measured onNVIDIA H100
declared hardwareNVIDIA H100
architecturessm_90
dtypesuint8

Benchmark evidence

1 measurement across 1 GPU, fastest first.

Operation / workload
Hardware
Latency
Rank
Observed
Histogramsuite of 6 cases
NVIDIA H100
704.3µs
#20 of 24
2026-02-27

Reported · How evidence levels are derived →

Source and license

sourceavailable
revision digestsha256:2471c7bfeff48861981f1f21a2acbf115a9000a1fbdd36915df8b38a078b75b1
license declaredunknown
license concludedunknown
authorsKernelAgent
imported2026-08-15

Techniques

Extracted from the mirrored source by pattern, never inferred. Each row cites its line.

num-warps = 4num_warps=4,
stages = 2for start in tl.range(0, BLOCK_SIZE, SUB_BLOCK, num_stages=2):

Kernel source

histogram_v2_H100_gpt-5_ka_submission.py129 lines
import torch
import triton
import triton.language as tl


@triton.jit
def _histogram_kernel(data_ptr, out_ptr, SIZE,
                      BLOCK_SIZE: tl.constexpr,
                      SUB_BLOCK: tl.constexpr):
    """
    Compute a 256-bin histogram for uint8 input values [0..255].

    Each Triton program ("block") processes a contiguous chunk of the input.
    Within a block, we build a local histogram in SRAM/registers by iterating
    over the chunk in SUB_BLOCK slices to keep working set small. We use a
    broadcasted equality (values[:, None] == bins[None, :]) masked by valid
    positions to count occurrences per bin, then reduce along the values axis.
    Finally, we atomically accumulate the per-block histogram into the global
    output buffer to combine results across blocks.

    Notes on fusion:
    - Counting and global accumulation are fused in a single kernel to avoid
      intermediate buffers and multiple launches. The only synchronization is
      the final atomic adds per bin per block, minimizing contention compared
      to per-element atomics.
    """
    # Program id and block range
    pid = tl.program_id(axis=0)
    block_start = pid * BLOCK_SIZE

    # Local 256-bin histogram in int32 (sufficient for counts up to SIZE per block)
    local_hist = tl.zeros((256,), dtype=tl.int32)

    # Bin indices [0..255] for broadcast comparisons
    bins = tl.arange(0, 256)

    # Process the chunk in SUB_BLOCK slices
    for start in tl.range(0, BLOCK_SIZE, SUB_BLOCK, num_stages=2):
        offs = block_start + start + tl.arange(0, SUB_BLOCK)
        mask = offs < SIZE
        # Load uint8 values; out-of-bounds masked to 0 and excluded by mask in the equality
        vals = tl.load(data_ptr + offs, mask=mask, other=tl.zeros((), dtype=tl.uint8))
        # Broadcast compare: shape [SUB_BLOCK, 256], mask invalid rows
        eq = (vals[:, None] == bins[None, :]) & mask[:, None]
        # Sum over rows (values) to produce counts per bin; cast bool -> int32 before sum
        local_hist += tl.sum(eq.to(tl.int32), 0)

    # Atomically add local histogram to global output (int64 bins)
    out_ptrs = out_ptr + bins
    tl.atomic_add(out_ptrs, local_hist.to(tl.int64))


def kernel_function(data: torch.Tensor, output: torch.Tensor = None):
    """
    Compute a 256-bin histogram over uint8 values [0..255] using a Triton kernel.

    Fused stages:
    - Single-pass counting within each block (local per-block histogram)
    - Atomic accumulation into the global 256-bin output
    This avoids multiple kernels or intermediate reduction buffers.

    Args:
        data: 1-D tensor of dtype torch.uint8 on CUDA device.
        output: Optional preallocated 1-D tensor of length 256, dtype torch.int64 on the same device.
                If provided, it will be zeroed and written in-place. If not provided, one is allocated.

    Returns:
        1-D torch.Tensor of shape [256], dtype torch.int64 on the same device as input.

    Runtime constraints adhered:
    - Wrapper performs only validation/allocation/launch; all computation is in Triton.
    - No torch.nn, torch.nn.functional, or PyTorch compute ops are used to form the histogram.
    """
    # Basic validation
    if not isinstance(data, torch.Tensor):
        raise TypeError("data must be a torch.Tensor")
    if data.device.type != "cuda":
        raise RuntimeError("CUDA device required")
    if data.dtype != torch.uint8:
        raise TypeError(f"data dtype must be torch.uint8, got {data.dtype}")
    if data.dim() != 1:
        raise ValueError(f"data must be 1-D, got shape {tuple(data.shape)}")

    SIZE = data.numel()

    # Prepare output buffer
    if output is None:
        output = torch.zeros(256, device=data.device, dtype=torch.int64)
    else:
        if output.device != data.device:
            raise RuntimeError("output must be on the same device as data")
        if output.dtype != torch.int64 or output.numel() != 256 or output.dim() != 1:
            raise ValueError("output must be a 1-D tensor of length 256 and dtype torch.int64")
        # Ensure starting from zero to avoid accumulation on garbage values
        output.zero_()

    # Configure launch
    BLOCK_SIZE = 1024  # power-of-two for good coalescing
    SUB_BLOCK = 128    # tile slice to keep working set small
    grid = (triton.cdiv(SIZE, BLOCK_SIZE),)

    # Launch Triton kernel
    _histogram_kernel[grid](
        data, output, SIZE,
        BLOCK_SIZE=BLOCK_SIZE,
        SUB_BLOCK=SUB_BLOCK,
        num_warps=4,
        num_stages=2,
    )

    return output

import inspect

def custom_kernel(input):
    sig = inspect.signature(kernel_function)
    num_params = len(sig.parameters)

    if len(input) == num_params:
        return kernel_function(*input)
    return kernel_function(input)


# Ensure deterministic cuBLAS.
import os
if os.environ.get("CUBLAS_WORKSPACE_CONFIG", "") not in (":4096:8", ":16:8"):
    os.environ["CUBLAS_WORKSPACE_CONFIG"] = ":4096:8"

scrolls · 129 lines total

Source code from GPU Mode and the KernelBot dataset · June 9 Researcher Reciprocity License v1.0

Changes from previous submission

Against this author's previous submission submission 490609.

- # kernel.py
- """
- Histogram (uint8 -> int64[256]) implemented in Triton.
-
- Pipeline / fusion note:
- - This operation is inherently a global reduction into 256 bins using atomic adds.
- - We additionally must *overwrite* the output (set bins to 0) before accumulation.
- - These two stages cannot be safely fused into a single kernel because Triton provides no
- global barrier across programs; clearing + accumulating in one launch would race.
- So we use two Triton kernels:
- (1) zero-out the 256-bin output
- (2) atomic accumulation over the input
- """
-
- from __future__ import annotations
-
import torch
import triton
import triton.language as tl
@triton.jit
- def _zero_256_i64_kernel(out_ptr, # *i64
- BLOCK: tl.constexpr):
- pid = tl.program_id(0)
- # Only one program is expected, but keep pid in case of accidental larger grids.
- offs = pid * BLOCK + tl.arange(0, BLOCK)
- mask = offs < 256
- tl.store(out_ptr + offs, tl.zeros([BLOCK], dtype=tl.int64), mask=mask)
+ def _histogram_kernel(data_ptr, out_ptr, SIZE,
+ BLOCK_SIZE: tl.constexpr,
+ SUB_BLOCK: tl.constexpr):
+ """
+ Compute a 256-bin histogram for uint8 input values [0..255].
+ Each Triton program ("block") processes a contiguous chunk of the input.
+ Within a block, we build a local histogram in SRAM/registers by iterating
+ over the chunk in SUB_BLOCK slices to keep working set small. We use a
+ broadcasted equality (values[:, None] == bins[None, :]) masked by valid
+ positions to count occurrences per bin, then reduce along the values axis.
+ Finally, we atomically accumulate the per-block histogram into the global
+ output buffer to combine results across blocks.
- @triton.jit
- def _hist_u8_to_i64_kernel(x_ptr, # *u8
- out_ptr, # *i64
- n_elements: tl.int32,
- BLOCK_SIZE: tl.constexpr):
- pid = tl.program_id(0)
- offs = pid * BLOCK_SIZE + tl.arange(0, BLOCK_SIZE)
- mask = offs < n_elements
+ Notes on fusion:
+ - Counting and global accumulation are fused in a single kernel to avoid
+ intermediate buffers and multiple launches. The only synchronization is
+ the final atomic adds per bin per block, minimizing contention compared
+ to per-element atomics.
+ """
+ # Program id and block range
+ pid = tl.program_id(axis=0)
+ block_start = pid * BLOCK_SIZE
- # Load uint8 values; cast to int32 for indexing.
- x = tl.load(x_ptr + offs, mask=mask, other=0).to(tl.int32) # in [0, 255]
+ # Local 256-bin histogram in int32 (sufficient for counts up to SIZE per block)
+ local_hist = tl.zeros((256,), dtype=tl.int32)
- # Atomic add into global histogram bins (int64 output).
- ones = tl.full([BLOCK_SIZE], 1, dtype=tl.int64)
- tl.atomic_add(out_ptr + x, ones, mask=mask)
+ # Bin indices [0..255] for broadcast comparisons
+ bins = tl.arange(0, 256)
+ # Process the chunk in SUB_BLOCK slices
+ for start in tl.range(0, BLOCK_SIZE, SUB_BLOCK, num_stages=2):
+ offs = block_start + start + tl.arange(0, SUB_BLOCK)
+ mask = offs < SIZE
+ # Load uint8 values; out-of-bounds masked to 0 and excluded by mask in the equality
+ vals = tl.load(data_ptr + offs, mask=mask, other=tl.zeros((), dtype=tl.uint8))
+ # Broadcast compare: shape [SUB_BLOCK, 256], mask invalid rows
+ eq = (vals[:, None] == bins[None, :]) & mask[:, None]
+ # Sum over rows (values) to produce counts per bin; cast bool -> int32 before sum
+ local_hist += tl.sum(eq.to(tl.int32), 0)
- def kernel_function(data, out: torch.Tensor | None = None) -> torch.Tensor:
- """
- Compute histogram of a CUDA uint8 1D tensor into 256 bins (int64), matching:
- out[...] = torch.bincount(data, minlength=256)
+ # Atomically add local histogram to global output (int64 bins)
+ out_ptrs = out_ptr + bins
+ tl.atomic_add(out_ptrs, local_hist.to(tl.int64))
- Allowed wrapper work: argument checks, allocation, and Triton launches only.
+
+ def kernel_function(data: torch.Tensor, output: torch.Tensor = None):
"""
- # Support alternate calling convention kernel_function((data, out))
- if out is None and isinstance(data, (tuple, list)) and len(data) == 2:
- data, out = data
+ Compute a 256-bin histogram over uint8 values [0..255] using a Triton kernel.
+ Fused stages:
+ - Single-pass counting within each block (local per-block histogram)
+ - Atomic accumulation into the global 256-bin output
+ This avoids multiple kernels or intermediate reduction buffers.
+
+ Args:
+ data: 1-D tensor of dtype torch.uint8 on CUDA device.
+ output: Optional preallocated 1-D tensor of length 256, dtype torch.int64 on the same device.
+ If provided, it will be zeroed and written in-place. If not provided, one is allocated.
+
+ Returns:
+ 1-D torch.Tensor of shape [256], dtype torch.int64 on the same device as input.
+
+ Runtime constraints adhered:
+ - Wrapper performs only validation/allocation/launch; all computation is in Triton.
+ - No torch.nn, torch.nn.functional, or PyTorch compute ops are used to form the histogram.
+ """
+ # Basic validation
if not isinstance(data, torch.Tensor):
- raise TypeError(f"data must be a torch.Tensor, got {type(data)}")
- if not data.is_cuda:
- raise ValueError("data must be a CUDA tensor")
+ raise TypeError("data must be a torch.Tensor")
+ if data.device.type != "cuda":
+ raise RuntimeError("CUDA device required")
if data.dtype != torch.uint8:
- raise ValueError(f"data must be torch.uint8, got {data.dtype}")
+ raise TypeError(f"data dtype must be torch.uint8, got {data.dtype}")
if data.dim() != 1:
- raise ValueError(f"data must be 1D, got shape={tuple(data.shape)}")
- if not data.is_contiguous():
- data = data.contiguous()
+ raise ValueError(f"data must be 1-D, got shape {tuple(data.shape)}")
- if out is None:
- out = torch.empty((256,), device=data.device, dtype=torch.int64)
- if not isinstance(out, torch.Tensor):
- raise TypeError(f"out must be a torch.Tensor, got {type(out)}")
- if out.device != data.device:
- raise ValueError("out must be on the same device as data")
- if out.dtype != torch.int64:
- raise ValueError(f"out must be torch.int64, got {out.dtype}")
- if out.shape != (256,):
- raise ValueError(f"out must have shape (256,), got {tuple(out.shape)}")
- if not out.is_contiguous():
- raise ValueError("out must be contiguous")
+ SIZE = data.numel()
- # Stage 1: overwrite output bins with zeros (in Triton, not PyTorch).
- _zero_256_i64_kernel[(1,)](out, BLOCK=256, num_warps=1)
+ # Prepare output buffer
+ if output is None:
+ output = torch.zeros(256, device=data.device, dtype=torch.int64)
+ else:
+ if output.device != data.device:
+ raise RuntimeError("output must be on the same device as data")
+ if output.dtype != torch.int64 or output.numel() != 256 or output.dim() != 1:
+ raise ValueError("output must be a 1-D tensor of length 256 and dtype torch.int64")
+ # Ensure starting from zero to avoid accumulation on garbage values
+ output.zero_()
- # Stage 2: atomic histogram accumulation.
- n_elements = data.numel()
- if n_elements == 0:
- return out
+ # Configure launch
+ BLOCK_SIZE = 1024 # power-of-two for good coalescing
+ SUB_BLOCK = 128 # tile slice to keep working set small
+ grid = (triton.cdiv(SIZE, BLOCK_SIZE),)
- BLOCK_SIZE = 1024
- grid = (triton.cdiv(n_elements, BLOCK_SIZE),)
- _hist_u8_to_i64_kernel[grid](data, out, n_elements, BLOCK_SIZE=BLOCK_SIZE, num_warps=4)
+ # Launch Triton kernel
+ _histogram_kernel[grid](
+ data, output, SIZE,
+ BLOCK_SIZE=BLOCK_SIZE,
+ SUB_BLOCK=SUB_BLOCK,
+ num_warps=4,
+ num_stages=2,
+ )
- return out
+ return output
import inspect
scrolls · 187 diff lines total

Best evidence level for this revision: reported

JSON