Skip to content
KernelIndex
Search⌘K

submission 878153

zack · python · License unknown

Use it

Vendorable · source mirrored · license unknownView source →

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

submission.py
curl "https://kernelindex.com/api/v1/implementations/kernelbot-cholesky-878153?include=source"
interfacepython
Compatibility
measured onNVIDIA B200
declared hardwareNVIDIA B200
architecturessm_100
dtypesfp32

Benchmark evidence

1 measurement across 1 GPU, fastest first.

Operation / workload
Hardware
Latency
Rank
Observed
NVIDIA B200
2.53ms
#323 of 337
2026-07-15

Reported · How evidence levels are derived →

Source and license

sourceavailable
revision digestsha256:399a386305bf8becfefa57b98a87d4e107526cc7ce28474397131d10a404e092
license declaredunknown
license concludedunknown
authorszack
imported2026-08-26

Techniques

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

shared-memoryextern __shared__ float smem[];

Kernel source

submission.py237 lines
"""
Batched dense Cholesky factorization for B200 (SM100a).

Contract
--------
Input  : A, a (batch, n, n) CUDA tensor in torch.float32, symmetric positive
         definite up to fp32 roundoff.
Output : L, a (batch, n, n) lower-triangular fp32 tensor with strictly positive
         diagonal such that A = L @ L^T.

Routing (by n; every benchmark entry has a single fixed n, so this is a
size-dispatch with zero intra-batch divergence, not per-matrix conditioning):

    n <=  32 : warp-per-matrix kernel   (many matrices per CTA, packed SMEM)
    n <= 256 : block-per-matrix kernel  (one matrix per CTA, packed SMEM)
    n >  256 : cusolver blocked potrf via torch.linalg.cholesky

Both custom kernels run a right-looking Cholesky on the packed lower triangle
held in shared memory, in fp32.  A = L L^T with cond ~ 2 keeps fp32 residual at
~1e-7 (validated against LAPACK across n and dynamic-range scale), so no
FP64/emulation is needed; the input arrives already SPD, so there is no Gram
formation and no condition-number squaring.
"""

import torch
from torch.utils.cpp_extension import load_inline

_CPP = r"""
#include <torch/extension.h>
void chol_launch(torch::Tensor A, torch::Tensor L);
"""

_CUDA = r"""
#include <torch/extension.h>
#include <cuda_runtime.h>
#include <math.h>

// Packed lower-triangular index: element (i,j), i >= j, at i*(i+1)/2 + j.
#define IDXP(i, j) ((i) * ((i) + 1) / 2 + (j))

// Map a linear position p in [0, T) to the p-th lower-triangular cell (i,j),
// i >= j, in row-major triangular order.  Exact for all p via the two
// correction loops around the floating-point seed.
__device__ __forceinline__ void tri_decode(int p, int &i, int &j) {
    i = (int)((sqrtf(8.0f * (float)p + 1.0f) - 1.0f) * 0.5f);
    while ((i + 1) * (i + 2) / 2 <= p) ++i;
    while (i * (i + 1) / 2 > p) --i;
    j = p - i * (i + 1) / 2;
}

// ---------------------------------------------------------------------------
// Warp-per-matrix kernel for n <= 32.  Each warp owns one matrix; WPB warps
// per block.  The matrix's packed lower triangle lives in per-warp SMEM.
// ---------------------------------------------------------------------------
template <int WPB>
__global__ void chol_warp_kernel(const float *__restrict__ A,
                                 float *__restrict__ Lout,
                                 int batch, int n) {
    extern __shared__ float smem[];
    const int warp = threadIdx.x >> 5;
    const int lane = threadIdx.x & 31;
    const int mat  = blockIdx.x * WPB + warp;
    const int tri  = n * (n + 1) / 2;
    float *s = smem + warp * tri;

    if (mat >= batch) return;

    const float *Amat = A + (size_t)mat * n * n;
    float *Lmat = Lout + (size_t)mat * n * n;

    // Load the lower triangle into packed SMEM.
    for (int p = lane; p < tri; p += 32) {
        int i, j;
        tri_decode(p, i, j);
        s[p] = Amat[(size_t)i * n + j];
    }
    __syncwarp();

    // Right-looking Cholesky.
    for (int k = 0; k < n; ++k) {
        float piv = sqrtf(s[IDXP(k, k)]);   // every lane reads the same cell
        __syncwarp();                        // all lanes read piv before overwrite
        for (int i = k + lane; i < n; i += 32)
            s[IDXP(i, k)] = s[IDXP(i, k)] / piv;
        __syncwarp();
        const int m = n - 1 - k;
        const int npair = m * (m + 1) / 2;
        for (int p = lane; p < npair; p += 32) {
            int ii, jj;
            tri_decode(p, ii, jj);
            const int i = k + 1 + ii;
            const int j = k + 1 + jj;
            s[IDXP(i, j)] -= s[IDXP(i, k)] * s[IDXP(j, k)];
        }
        __syncwarp();
    }

    // Emit full n x n: lower triangle from s, strict upper triangle zeroed.
    for (int p = lane; p < n * n; p += 32) {
        const int i = p / n;
        const int j = p - i * n;
        Lmat[p] = (j <= i) ? s[IDXP(i, j)] : 0.0f;
    }
}

// ---------------------------------------------------------------------------
// Block-per-matrix kernel for n <= 256.  One matrix per CTA; the packed lower
// triangle lives in dynamic SMEM (n=256 -> ~128.5 KB, within the 227 KB cap).
// ---------------------------------------------------------------------------
__global__ void chol_block_kernel(const float *__restrict__ A,
                                  float *__restrict__ Lout,
                                  int batch, int n) {
    extern __shared__ float s[];
    const int mat = blockIdx.x;
    const int tid = threadIdx.x;
    const int nt  = blockDim.x;
    const int tri = n * (n + 1) / 2;

    if (mat >= batch) return;

    const float *Amat = A + (size_t)mat * n * n;
    float *Lmat = Lout + (size_t)mat * n * n;

    for (int p = tid; p < tri; p += nt) {
        int i, j;
        tri_decode(p, i, j);
        s[p] = Amat[(size_t)i * n + j];
    }
    __syncthreads();

    for (int k = 0; k < n; ++k) {
        float piv = sqrtf(s[IDXP(k, k)]);   // every thread reads the same cell
        __syncthreads();                     // all read piv before overwrite
        for (int i = k + tid; i < n; i += nt)
            s[IDXP(i, k)] = s[IDXP(i, k)] / piv;
        __syncthreads();
        const int m = n - 1 - k;
        const int npair = m * (m + 1) / 2;
        for (int p = tid; p < npair; p += nt) {
            int ii, jj;
            tri_decode(p, ii, jj);
            const int i = k + 1 + ii;
            const int j = k + 1 + jj;
            s[IDXP(i, j)] -= s[IDXP(i, k)] * s[IDXP(j, k)];
        }
        __syncthreads();
    }

    for (int p = tid; p < n * n; p += nt) {
        const int i = p / n;
        const int j = p - i * n;
        Lmat[p] = (j <= i) ? s[IDXP(i, j)] : 0.0f;
    }
}

void chol_launch(torch::Tensor A, torch::Tensor L) {
    const int batch = (int)A.size(0);
    const int n     = (int)A.size(2);
    const int tri   = n * (n + 1) / 2;

    // Kernels run on the default execution queue, which is the same queue the
    // caller enqueues on, so no explicit queue handle is named or needed.
    const float *Ap = A.data_ptr<float>();
    float *Lp = L.data_ptr<float>();

    if (n <= 32) {
        constexpr int WPB = 8;
        const int grid = (batch + WPB - 1) / WPB;
        const size_t sh = (size_t)WPB * tri * sizeof(float);
        chol_warp_kernel<WPB><<<grid, WPB * 32, sh>>>(Ap, Lp, batch, n);
    } else {
        const int nt = 256;
        const size_t sh = (size_t)tri * sizeof(float);
        cudaFuncSetAttribute(chol_block_kernel,
                             cudaFuncAttributeMaxDynamicSharedMemorySize,
                             (int)sh);
        chol_block_kernel<<<batch, nt, sh>>>(Ap, Lp, batch, n);
    }
}
"""

_mod = load_inline(
    name="chol_b200",
    cpp_sources=_CPP,
    cuda_sources=_CUDA,
    functions=["chol_launch"],
    extra_cuda_cflags=["-O3"],
    extra_cflags=["-O3"],
    verbose=False,
)


def _unwrap(x):
    # Accept the SPD tensor directly, or the common container shapes the harness
    # may hand over (tuple/list/dict with a single tensor payload).
    if isinstance(x, torch.Tensor):
        return x
    if isinstance(x, dict):
        for v in x.values():
            if isinstance(v, torch.Tensor):
                return v
    if isinstance(x, (tuple, list)):
        for v in x:
            if isinstance(v, torch.Tensor):
                return v
    raise TypeError("expected a torch.Tensor SPD input")


def custom_kernel(data):
    A = _unwrap(data)

    squeeze = False
    if A.dim() == 2:
        A = A.unsqueeze(0)
        squeeze = True

    if A.dtype != torch.float32:
        A = A.to(torch.float32)
    A = A.contiguous()

    n = A.size(-1)

    if n <= 256:
        L = torch.empty_like(A)
        _mod.chol_launch(A, L)
    else:
        L = torch.linalg.cholesky(A)

    if squeeze:
        L = L.squeeze(0)
    return L


# Common aliases so the harness finds the entry point under any of these names.
cholesky = custom_kernel
solve = custom_kernel
run = custom_kernel
scrolls · 237 lines total

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

Best evidence level for this revision: reported

JSON