submission 822973
chiendb · python · License unknown
Use it
Vendorable · source mirrored · license unknownView source →
No package. Vendor the mirrored source: 343 lines, June 9 Researcher Reciprocity License v1.0.
submission.py
curl "https://kernelindex.com/api/v1/implementations/kernelbot-qr-v2-822973?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
Reported · How evidence levels are derived →
Source and license
sourceavailable
revision digestsha256:921152ac7c4a1314eb6a43c96bb869c25cba739fd582f319478db0572a240e34
license declaredunknown
license concludedunknown
authorschiendb
imported2026-08-26
Techniques
Extracted from the mirrored source by pattern, never inferred. Each row cites its line.
shared-memory
extern __shared__ float smem[];Kernel source
submission.py343 lines
#!POPCORN leaderboard qr_v2
#!POPCORN gpu B200
# Batched square compact-Householder QR (geqrf-compatible (H, tau) output).
#
# Two execution paths, routed on shape only (never on matrix values, so the
# "mixed" anti-cheat rule is satisfied) -- both run the same backward-stable
# Householder math, so every conditioning profile passes the FP32 gate:
#
# * Blocked batched (n <= 2048): right-looking blocked Householder with the WY
# representation. A fused panel-factorization kernel (one block per matrix,
# panel resident in shared memory) produces R, the reflectors V, tau and the
# pb x pb WY factor T in-kernel; the trailing update is three batched cuBLAS
# SGEMMs (BLAS-3). Keeping the T-factor in-kernel matters on the leaderboard
# runtime, where per-launch overhead is high -- the blocked path's launch
# count, not its FLOPs, is what dominates there, so we minimize launches
# (everything the panel can do, it does in one kernel).
#
# * cuSOLVER (n > 2048, i.e. n=4096 b=2): too few, too large matrices -- the
# blocked panel would under-occupy the GPU and explode the per-panel launch
# count, so each matrix goes through cuSOLVER's own blocked geqrf in a loop.
# (cuSOLVER is column-major, so this path clones transposed and returns a
# transposed view.)
#
# Everything runs in the default execution queue the harness provides; no extra
# queues are created.
import torch
from torch.utils.cpp_extension import load_inline
from task import input_t, output_t
CUDA_SRC = r"""
#include <ATen/cuda/CUDAContext.h>
#include <cublas_v2.h>
#include <cusolverDn.h>
#include <vector>
// ---------------------------------------------------------------------------
__device__ __forceinline__ float warp_reduce_sum(float v) {
#pragma unroll
for (int o = 16; o > 0; o >>= 1) v += __shfl_down_sync(0xffffffffu, v, o);
return v;
}
__device__ __forceinline__ float block_reduce_sum(float val, float* scratch) {
int lane = threadIdx.x & 31, wid = threadIdx.x >> 5;
val = warp_reduce_sum(val);
if (lane == 0) scratch[wid] = val;
__syncthreads();
int nwarps = (blockDim.x + 31) >> 5;
val = (threadIdx.x < nwarps) ? scratch[lane] : 0.0f;
if (wid == 0) {
val = warp_reduce_sum(val);
if (lane == 0) scratch[0] = val;
}
__syncthreads();
float t = scratch[0];
__syncthreads();
return t;
}
// Fused panel factorization for a column-major matrix M (lda = n).
// Factors the panel rows[j, n) x cols[j, j+pb): writes R (panel upper incl diag),
// essential reflectors (panel strictly-lower), tau[j..j+pb), and the WY T-factor
// (pb x pb, upper-triangular) into Tg (stride pb*pb per matrix, column-major pb).
// One block per matrix; panel resident in shared memory.
//
// Shared layout (floats): P[h*pb] | BT[pb*pb] | scr[<=32] | (h <= n)
__global__ void panel_factor(float* __restrict__ A, float* __restrict__ tau,
float* __restrict__ Tg, float* __restrict__ Vg,
int n, int j, int pb) {
const int b = blockIdx.x;
const int tid = threadIdx.x;
const int nt = blockDim.x;
const int h = n - j; // panel height
float* __restrict__ M = A + (long long)b * n * n;
float* __restrict__ T = Tg + (long long)b * pb * pb;
float* __restrict__ tA = tau + (long long)b * n;
extern __shared__ float smem[];
float* P = smem; // h*pb, column-major (P[r + c*h])
float* BT = P + (long long)h * pb; // pb*pb (B then reused for T), col-major
float* zcol = BT + pb * pb; // pb (LARFT temp)
float* scr = zcol + pb; // <=32
// 1. load panel into shared. M is ROW-MAJOR: element (row j+r, col j+c) at
// (j+r)*n + (j+c). Shared P stays column-major (P[r + c*h]).
for (long long idx = tid; idx < (long long)h * pb; idx += nt) {
int c = idx / h, r = idx - (long long)c * h;
P[r + (long long)c * h] = M[(long long)(j + r) * n + (j + c)];
}
__syncthreads();
// 2. factor the panel in shared (BLAS-2)
for (int k = 0; k < pb; ++k) {
// reflector for column k, rows [k, h)
float local = 0.0f;
for (int r = k + 1 + tid; r < h; r += nt) {
float x = P[r + (long long)k * h];
local += x * x;
}
float xnorm2 = block_reduce_sum(local, scr);
float alpha = P[k + (long long)k * h];
__syncthreads();
float tauk;
if (xnorm2 > 0.0f) {
float beta = -copysignf(sqrtf(alpha * alpha + xnorm2), alpha);
tauk = (beta - alpha) / beta;
float inv = 1.0f / (alpha - beta);
if (tid == 0) { P[k + (long long)k * h] = beta; tA[j + k] = tauk; }
for (int r = k + 1 + tid; r < h; r += nt)
P[r + (long long)k * h] *= inv;
} else {
tauk = 0.0f;
if (tid == 0) tA[j + k] = 0.0f;
}
__syncthreads();
// apply H_k = I - tauk v v^T (v[k]=1, v[r]=P[r,k]) to columns k+1..pb-1
if (tauk != 0.0f) {
int wid = tid >> 5, lane = tid & 31, nwarps = nt >> 5;
for (int c = k + 1 + wid; c < pb; c += nwarps) {
float w = 0.0f;
// r=k term: v=1
if (lane == 0) w += P[k + (long long)c * h];
for (int r = k + 1 + lane; r < h; r += 32)
w += P[r + (long long)k * h] * P[r + (long long)c * h];
w = warp_reduce_sum(w);
w = __shfl_sync(0xffffffffu, w, 0) * tauk;
if (lane == 0) P[k + (long long)c * h] -= w; // r=k, v=1
for (int r = k + 1 + lane; r < h; r += 32)
P[r + (long long)c * h] -= w * P[r + (long long)k * h];
}
}
__syncthreads();
}
// 3. write panel back to M (row-major) and materialize V (unit lower-trapezoidal)
// into Vg row-major (h x pb, row stride pb, matrix stride n*pb).
float* __restrict__ V = Vg + (long long)b * n * pb;
for (long long idx = tid; idx < (long long)h * pb; idx += nt) {
int c = idx / h, r = idx - (long long)c * h;
float val = P[r + (long long)c * h];
M[(long long)(j + r) * n + (j + c)] = val;
V[(long long)r * pb + c] = (r < c) ? 0.0f : (r == c ? 1.0f : val);
}
// 4. build B[m,c] = v_m^T v_c (m<c) into BT (col-major pb), upper part only.
// v_c has unit diag at row c: v_c[c]=1, v_c[r]=P[r,c] for r>c, 0 for r<c.
// B[m,c] = P[c,m]*1 + sum_{r=c+1..h-1} P[r,m]*P[r,c] (since m<c)
{
int wid = tid >> 5, lane = tid & 31, nwarps = nt >> 5;
int npair = pb * pb;
for (int p = wid; p < npair; p += nwarps) {
int c = p / pb, m = p - c * pb;
if (m < c) {
float s = 0.0f;
if (lane == 0) s += P[c + (long long)m * h]; // r=c term
for (int r = c + 1 + lane; r < h; r += 32)
s += P[r + (long long)m * h] * P[r + (long long)c * h];
s = warp_reduce_sum(s);
if (lane == 0) BT[m + c * pb] = s;
}
}
}
__syncthreads();
// 5. LARFT forward recurrence -> T (overwrite BT). T upper-tri, col-major pb.
// z[p] = -tau_c * B[p,c]; T[0:c,c] = T[0:c,0:c] @ z; T[c,c]=tau_c.
// Sequential over c; threads parallel over rows m. zcol decouples the read
// of B[:,c] from the write of T[:,c] so there is no in-place race.
for (int c = 0; c < pb; ++c) {
float tauc = tA[j + c];
for (int p = tid; p < c; p += nt)
zcol[p] = -tauc * BT[p + c * pb]; // -tau_c * B[p,c]
__syncthreads();
for (int m = tid; m < c; m += nt) {
float acc = 0.0f;
for (int p = m; p < c; ++p) // T[m,p] (p<c, built) * z[p]
acc += BT[m + p * pb] * zcol[p];
BT[m + c * pb] = acc;
}
__syncthreads();
if (tid == 0) BT[c + c * pb] = tauc;
__syncthreads();
}
// write T to global
for (long long idx = tid; idx < (long long)pb * pb; idx += nt) {
int c = idx / pb, m = idx - (long long)c * pb;
T[m + c * pb] = (m <= c) ? BT[m + c * pb] : 0.0f;
}
}
// ---------------------------------------------------------------------------
// Own library handles. A freshly created handle uses the default execution
// queue (the same one our <<<>>> kernels use) -- so kernels and library calls
// stay correctly ordered without ever naming or creating an extra queue.
static cusolverDnHandle_t g_solv = nullptr;
static cublasHandle_t g_blas = nullptr;
#define CK_CUDA(x) do { cudaError_t e_=(x); TORCH_CHECK(e_==cudaSuccess, "cuda: ", cudaGetErrorString(e_)); } while(0)
#define CK_BLAS(x) do { cublasStatus_t s_=(x); TORCH_CHECK(s_==CUBLAS_STATUS_SUCCESS, "cublas status ", (int)s_); } while(0)
#define CK_SOLV(x) do { cusolverStatus_t s_=(x); TORCH_CHECK(s_==CUSOLVER_STATUS_SUCCESS, "cusolver status ", (int)s_); } while(0)
std::vector<torch::Tensor> qr_compact(torch::Tensor A) {
TORCH_CHECK(A.is_cuda() && A.dim() == 3 && A.size(1) == A.size(2));
TORCH_CHECK(A.scalar_type() == torch::kFloat32);
int batch = A.size(0), n = A.size(1);
auto tau = torch::empty({batch, n}, A.options());
float* Tp = tau.data_ptr<float>();
if (batch == 0 || n == 0)
return {A.clone(), tau};
// Nothing below names or creates an execution queue: kernels launch in the
// default queue and the cuBLAS / cuSOLVER handles default to it too, so every
// kernel and library call stays correctly ordered.
//
// Route only the few-huge-matrix case (n>2048, i.e. n=4096 b=2) to cuSOLVER;
// n=2048 b=8 goes through the blocked path (cuSOLVER's B200 FP32 geqrf is ~3x
// slower there). cuSOLVER is column-major, so its path clones into a transposed
// buffer and returns a transposed view; the blocked path below works row-major.
bool use_cusolver = (n > 2048);
if (use_cusolver) {
auto At = A.transpose(-1, -2).contiguous(); // col-major working buffer
float* Ap = At.data_ptr<float>();
if (!g_solv) CK_SOLV(cusolverDnCreate(&g_solv));
int lwork = 0;
CK_SOLV(cusolverDnSgeqrf_bufferSize(g_solv, n, n, Ap, n, &lwork));
auto work = torch::empty({lwork}, A.options());
auto info = torch::empty({1}, torch::TensorOptions().dtype(torch::kInt32).device(A.device()));
for (int b = 0; b < batch; ++b)
CK_SOLV(cusolverDnSgeqrf(g_solv, n, n, Ap + (long long)b * n * n, n,
Tp + (long long)b * n, work.data_ptr<float>(), lwork,
info.data_ptr<int>()));
CK_CUDA(cudaGetLastError());
return {At.transpose(-1, -2), tau};
}
// Blocked batched path (row-major throughout; clone is a plain copy).
auto At = A.clone();
float* Ap = At.data_ptr<float>();
int dev = A.device().index();
int shmem_optin = at::cuda::getDeviceProperties(dev)->sharedMemPerBlockOptin;
// Use the device's full opt-in shared memory (B200 = 227 KB). This lets the
// panel fit for larger n (e.g. n=2048 -> pb=24), which is what makes the
// blocked path beat cuSOLVER's slow B200 geqrf on the n=2048 batch.
int budget = shmem_optin - 256; // small margin (need[] has +64 pad)
// pick pb (multiple of 8, capped at 48) so n*pb + pb*pb + pb + 32 floats fit.
// Larger pb => fewer panels => fewer kernel/GEMM launches and a larger k in the
// trailing GEMMs (better arithmetic intensity); the leaderboard runtime rewards
// both. Capped at 48 to keep >=2 panel blocks resident per SM at n=512.
int pb = 0;
for (int cand = 8; cand <= 48; cand += 8) {
long long need = ((long long)n * cand + cand * cand + cand + 64) * sizeof(float);
if (need <= budget) pb = cand; else break;
}
if (pb == 0) pb = 8;
if (pb > n) pb = n;
auto Vg = torch::empty({batch, (long long)n * pb}, A.options());
auto Tg = torch::empty({batch, (long long)pb * pb}, A.options());
auto W1 = torch::empty({batch, (long long)pb * n}, A.options());
auto W2 = torch::empty({batch, (long long)pb * n}, A.options());
float* Vp = Vg.data_ptr<float>();
float* Tgp = Tg.data_ptr<float>();
float* W1p = W1.data_ptr<float>();
float* W2p = W2.data_ptr<float>();
// Use our OWN cuBLAS handle (default queue), not torch's getCurrentCUDABlasHandle
// -- torch's handle is bound to torch's current queue, which the harness may set
// to a non-default one for timing; that would run the GEMMs out of order with our
// default-queue kernels. Our handle defaults to the same queue as the kernels.
if (!g_blas) {
CK_BLAS(cublasCreate(&g_blas));
// Force true IEEE FP32 in the trailing-update GEMMs. On data-center GPUs
// cuBLAS may otherwise use TF32 tensor cores for SGEMM (~10-bit mantissa),
// which blows the FP32 residual gate on ill-conditioned / mixed batches.
CK_BLAS(cublasSetMathMode(g_blas, CUBLAS_PEDANTIC_MATH));
}
cublasHandle_t hbl = g_blas;
const float one = 1.0f, zero = 0.0f, neg = -1.0f;
// Thread count by how well the batch fills the GPU. With many matrices
// (batch >= 128) there are already enough blocks to saturate the 148 SMs, so
// 256 threads/block is best and 512 only adds sync overhead (measured: n=512
// b=640 regresses with 512). With few matrices (n=1024 b=60, n=2048 b=8) the
// panel is block-starved, so 512 threads/block adds useful parallelism
// (measured: n=1024 -3%, n=2048 -5%). Small n stays 256 (a wide block idles).
int threads = (batch < 128 && n >= 256) ? 512 : 256;
size_t max_shmem = ((long long)n * pb + pb * pb + pb + 64) * sizeof(float);
CK_CUDA(cudaFuncSetAttribute(panel_factor, cudaFuncAttributeMaxDynamicSharedMemorySize, (int)max_shmem));
for (int j = 0; j < n; j += pb) {
int pbj = (pb < n - j) ? pb : (n - j);
int h = n - j;
size_t shmem = ((long long)h * pbj + pbj * pbj + pbj + 64) * sizeof(float);
panel_factor<<<batch, threads, shmem>>>(Ap, Tp, Tgp, Vp, n, j, pbj);
CK_CUDA(cudaGetLastError());
int jb = j + pbj;
int tcols = n - jb;
if (tcols > 0) {
// Row-major trailing update C = C - V (T^T (V^T C)), C = M[j:n, jb:n].
// cuBLAS is column-major; the col-major view of row-major X (lda=row
// stride) is X^T, so we operate on transposes:
// Ccm=C^T (tcols x h, lda=n), Vcm=V^T (pb x h, lda=pb), Tcm-view=T.
// W1cm = Ccm * V (=W1^T) -> opN(Ccm), opT(Vcm)
// W2cm = W1cm * T (=W2^T) -> opN, opN
// Ccm -= W2cm * Vcm (C -=V W2) -> opN, opN, alpha=-1 beta=1
float* Cp = Ap + (long long)j * n + jb; // row-major C base (j,jb)
long long sC = (long long)n * n, sV = (long long)n * pb,
sT = (long long)pb * pb, sW = (long long)pb * n;
CK_BLAS(cublasSgemmStridedBatched(hbl, CUBLAS_OP_N, CUBLAS_OP_T,
tcols, pbj, h, &one, Cp, n, sC, Vp, pb, sV, &zero, W1p, tcols, sW, batch));
CK_BLAS(cublasSgemmStridedBatched(hbl, CUBLAS_OP_N, CUBLAS_OP_N,
tcols, pbj, pbj, &one, W1p, tcols, sW, Tgp, pb, sT, &zero, W2p, tcols, sW, batch));
CK_BLAS(cublasSgemmStridedBatched(hbl, CUBLAS_OP_N, CUBLAS_OP_N,
tcols, h, pbj, &neg, W2p, tcols, sW, Vp, pb, sV, &one, Cp, n, sC, batch));
}
}
CK_CUDA(cudaGetLastError());
return {At, tau};
}
"""
CPP_SRC = r"""
std::vector<torch::Tensor> qr_compact(torch::Tensor A);
"""
_module = load_inline(
name="qr_v2_blocked",
cpp_sources=[CPP_SRC],
cuda_sources=[CUDA_SRC],
functions=["qr_compact"],
extra_cuda_cflags=["-O3"],
extra_ldflags=["-lcublas", "-lcusolver"],
verbose=False,
)
def custom_kernel(data: input_t) -> output_t:
H, tau = _module.qr_compact(data)
return (H, tau)
scrolls · 343 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