Skip to content

Instantly share code, notes, and snippets.

@egorsmkv
Created June 12, 2026 23:13
Show Gist options
  • Select an option

  • Save egorsmkv/55dc05f165a32c159f2bd929b98df15c to your computer and use it in GitHub Desktop.

Select an option

Save egorsmkv/55dc05f165a32c159f2bd929b98df15c to your computer and use it in GitHub Desktop.
import os
import torch
from torch.utils.cpp_extension import load_inline
_qr_mod = None
CUDA_SRC = r"""
#include <torch/extension.h>
#include <ATen/cuda/CUDAContext.h>
#include <c10/cuda/CUDAGuard.h>
#include <c10/cuda/CUDAException.h>
#include <cublas_v2.h>
#include <vector>
#include <algorithm>
#include <cmath>
#define CUBLAS_CHECK(cmd) \
do { \
cublasStatus_t status_ = (cmd); \
TORCH_CHECK(status_ == CUBLAS_STATUS_SUCCESS, \
"cuBLAS failure, status=", static_cast<int>(status_), \
" at ", __FILE__, ":", __LINE__); \
} while (0)
static constexpr int QR_BLOCK = 512;
static constexpr int MAX_NB = 64;
__inline__ __device__ float warp_sum(float v) {
unsigned mask = 0xffffffffu;
v += __shfl_down_sync(mask, v, 16);
v += __shfl_down_sync(mask, v, 8);
v += __shfl_down_sync(mask, v, 4);
v += __shfl_down_sync(mask, v, 2);
v += __shfl_down_sync(mask, v, 1);
return v;
}
__inline__ __device__ float block_sum(float v, float* smem) {
int tid = threadIdx.x;
int lane = tid & 31;
int wid = tid >> 5;
int nwarps = (blockDim.x + 31) >> 5;
v = warp_sum(v);
if (lane == 0) smem[wid] = v;
__syncthreads();
v = (tid < nwarps) ? smem[lane] : 0.0f;
if (wid == 0) v = warp_sum(v);
if (tid == 0) smem[0] = v;
__syncthreads();
return smem[0];
}
__global__ void to_col_major_kernel(
const float* __restrict__ A,
float* __restrict__ C,
long long total,
int n) {
long long stride = (long long)blockDim.x * gridDim.x;
for (long long idx = (long long)blockIdx.x * blockDim.x + threadIdx.x;
idx < total;
idx += stride) {
long long nn = (long long)n * n;
long long b = idx / nn;
long long rem = idx - b * nn;
int r = (int)(rem / n);
int c = (int)(rem - (long long)r * n);
C[b * nn + r + (long long)c * n] =
A[b * nn + (long long)r * n + c];
}
}
__global__ void from_col_major_kernel(
const float* __restrict__ C,
float* __restrict__ H,
long long total,
int n) {
long long stride = (long long)blockDim.x * gridDim.x;
for (long long idx = (long long)blockIdx.x * blockDim.x + threadIdx.x;
idx < total;
idx += stride) {
long long nn = (long long)n * n;
long long b = idx / nn;
long long rem = idx - b * nn;
int r = (int)(rem / n);
int c = (int)(rem - (long long)r * n);
H[b * nn + (long long)r * n + c] =
C[b * nn + r + (long long)c * n];
}
}
// Unblocked Householder QR of one panel per batch item.
// Only updates columns inside the current panel; the trailing matrix is updated later
// by the compact WY block reflector through cuBLAS.
__global__ void panel_qr_kernel(
float* __restrict__ C,
float* __restrict__ tau,
int n,
int k,
int ib) {
extern __shared__ float sh[];
int b = blockIdx.x;
int tid = threadIdx.x;
int lane = tid & 31;
int warp = tid >> 5;
int nwarps = blockDim.x >> 5;
long long base = (long long)b * n * n;
for (int jj = 0; jj < ib; ++jj) {
int col = k + jj;
float ss = 0.0f;
for (int r = col + 1 + tid; r < n; r += blockDim.x) {
float x = C[base + r + (long long)col * n];
ss += x * x;
}
float sigma = block_sum(ss, sh);
if (tid == 0) {
float alpha = C[base + col + (long long)col * n];
float beta = alpha;
float tauv = 0.0f;
float inv = 0.0f;
if (sigma == 0.0f) {
if (alpha < 0.0f) {
beta = -alpha;
tauv = 2.0f;
}
} else {
float norm = sqrtf(alpha * alpha + sigma);
beta = (alpha >= 0.0f) ? -norm : norm;
tauv = (beta - alpha) / beta;
inv = 1.0f / (alpha - beta);
}
C[base + col + (long long)col * n] = beta;
tau[(long long)b * n + col] = tauv;
sh[0] = tauv;
sh[1] = inv;
}
__syncthreads();
float tauv = sh[0];
float inv = sh[1];
if (inv != 0.0f) {
for (int r = col + 1 + tid; r < n; r += blockDim.x) {
C[base + r + (long long)col * n] *= inv;
}
}
__syncthreads();
// Apply current reflector to the rest of the panel.
if (tauv != 0.0f) {
for (int pp = jj + 1 + warp; pp < ib; pp += nwarps) {
int j = k + pp;
float dot = (lane == 0)
? C[base + col + (long long)j * n]
: 0.0f;
for (int r = col + 1 + lane; r < n; r += 32) {
dot += C[base + r + (long long)col * n] *
C[base + r + (long long)j * n];
}
dot = warp_sum(dot);
float w = tauv * dot;
if (lane == 0) {
C[base + col + (long long)j * n] -= w;
}
for (int r = col + 1 + lane; r < n; r += 32) {
C[base + r + (long long)j * n] -=
C[base + r + (long long)col * n] * w;
}
}
}
__syncthreads();
}
}
// Pack the panel Householder vectors into dense V, column-major, lda=m.
// V is m x ib, where m = n-k.
__global__ void pack_v_kernel(
const float* __restrict__ C,
float* __restrict__ V,
long long total,
int n,
int k,
int m,
int ib,
long long strideV) {
long long step = (long long)blockDim.x * gridDim.x;
for (long long idx = (long long)blockIdx.x * blockDim.x + threadIdx.x;
idx < total;
idx += step) {
long long per = (long long)m * ib;
int b = (int)(idx / per);
long long rem = idx - (long long)b * per;
int row = (int)(rem % m);
int col = (int)(rem / m);
float val = 0.0f;
if (row == col) {
val = 1.0f;
} else if (row > col) {
long long cbase = (long long)b * n * n;
val = C[cbase + (k + row) + (long long)(k + col) * n];
}
V[(long long)b * strideV + row + (long long)col * m] = val;
}
}
// Form T for compact WY: H_panel = I - V T V^T = H_0 H_1 ... H_{ib-1}.
__global__ void form_t_kernel(
const float* __restrict__ C,
const float* __restrict__ tau,
float* __restrict__ T,
int n,
int k,
int m,
int ib,
long long strideT) {
extern __shared__ float shared[];
float* red = shared;
float* tmp = shared + 32;
int b = blockIdx.x;
int tid = threadIdx.x;
long long cbase = (long long)b * n * n;
float* Tb = T + (long long)b * strideT;
for (int idx = tid; idx < ib * ib; idx += blockDim.x) {
Tb[idx] = 0.0f;
}
__syncthreads();
for (int i = 0; i < ib; ++i) {
float taui = tau[(long long)b * n + k + i];
if (taui != 0.0f) {
for (int j = 0; j < i; ++j) {
float s = 0.0f;
// dot(v_j, v_i), starting at row i because v_i is zero above i.
for (int row = i + tid; row < m; row += blockDim.x) {
float vi = (row == i)
? 1.0f
: C[cbase + (k + row) + (long long)(k + i) * n];
// Since row >= i > j, this is below the diagonal of v_j.
float vj = C[cbase + (k + row) + (long long)(k + j) * n];
s += vj * vi;
}
float dot = block_sum(s, red);
if (tid == 0) {
Tb[j + (long long)i * ib] = -taui * dot;
}
__syncthreads();
}
if (tid == 0) {
// T(0:i, i) = T(0:i, 0:i) * T(0:i, i)
for (int r = 0; r < i; ++r) {
float acc = 0.0f;
for (int l = 0; l < i; ++l) {
acc += Tb[r + (long long)l * ib] * Tb[l + (long long)i * ib];
}
tmp[r] = acc;
}
for (int r = 0; r < i; ++r) {
Tb[r + (long long)i * ib] = tmp[r];
}
Tb[i + (long long)i * ib] = taui;
}
}
__syncthreads();
}
}
std::vector<torch::Tensor> qr_forward(torch::Tensor A) {
TORCH_CHECK(A.is_cuda(), "A must be CUDA");
TORCH_CHECK(A.scalar_type() == at::kFloat, "A must be torch.float32");
TORCH_CHECK(A.dim() == 3, "A must have shape [batch, n, n]");
TORCH_CHECK(A.size(1) == A.size(2), "A must be square");
c10::cuda::OptionalCUDAGuard guard(A.device());
auto Ac = A.contiguous();
int64_t B64 = Ac.size(0);
int64_t n64 = Ac.size(1);
TORCH_CHECK(n64 <= INT_MAX, "n too large");
int B = (int)B64;
int n = (int)n64;
auto C = torch::empty_like(Ac);
auto H = torch::empty_like(Ac);
auto tau = torch::empty({B64, n64}, Ac.options());
if (B == 0 || n == 0) {
return {H, tau};
}
auto stream = at::cuda::getCurrentCUDAStream();
long long total = (long long)B * n * n;
int threads = 256;
int blocks = (int)std::min<long long>((total + threads - 1) / threads, 65535LL);
to_col_major_kernel<<<blocks, threads, 0, stream>>>(
Ac.data_ptr<float>(), C.data_ptr<float>(), total, n);
C10_CUDA_KERNEL_LAUNCH_CHECK();
// Use smaller panels for 512-ish cases, larger for 1024+.
int nb = (n >= 1024) ? 64 : 32;
nb = std::min(nb, MAX_NB);
long long strideV = (long long)n * MAX_NB;
long long strideT = (long long)MAX_NB * MAX_NB;
long long strideW = (long long)MAX_NB * n;
long long strideCmat = (long long)n * n;
auto V = torch::empty({B64, strideV}, Ac.options());
auto T = torch::empty({B64, strideT}, Ac.options());
auto W = torch::empty({B64, strideW}, Ac.options());
auto W2 = torch::empty({B64, strideW}, Ac.options());
cublasHandle_t handle = at::cuda::getCurrentCUDABlasHandle();
CUBLAS_CHECK(cublasSetStream(handle, stream));
cublasMath_t old_math;
cublasPointerMode_t old_ptr;
CUBLAS_CHECK(cublasGetMathMode(handle, &old_math));
CUBLAS_CHECK(cublasGetPointerMode(handle, &old_ptr));
// Important for QR correctness: avoid TF32 tensor-core accumulation.
CUBLAS_CHECK(cublasSetMathMode(handle, CUBLAS_PEDANTIC_MATH));
CUBLAS_CHECK(cublasSetPointerMode(handle, CUBLAS_POINTER_MODE_HOST));
const float one = 1.0f;
const float zero = 0.0f;
const float minus_one = -1.0f;
float* Cptr = C.data_ptr<float>();
float* tauptr = tau.data_ptr<float>();
float* Vptr = V.data_ptr<float>();
float* Tptr = T.data_ptr<float>();
float* Wptr = W.data_ptr<float>();
float* W2ptr = W2.data_ptr<float>();
for (int k = 0; k < n; k += nb) {
int ib = std::min(nb, n - k);
int m = n - k;
int nt = n - k - ib;
panel_qr_kernel<<<B, QR_BLOCK, 32 * sizeof(float), stream>>>(
Cptr, tauptr, n, k, ib);
C10_CUDA_KERNEL_LAUNCH_CHECK();
if (nt > 0) {
long long vtotal = (long long)B * m * ib;
int vblocks = (int)std::min<long long>(
(vtotal + threads - 1) / threads, 65535LL);
pack_v_kernel<<<vblocks, threads, 0, stream>>>(
Cptr, Vptr, vtotal, n, k, m, ib, strideV);
C10_CUDA_KERNEL_LAUNCH_CHECK();
form_t_kernel<<<B, QR_BLOCK, (32 + MAX_NB) * sizeof(float), stream>>>(
Cptr, tauptr, Tptr, n, k, m, ib, strideT);
C10_CUDA_KERNEL_LAUNCH_CHECK();
float* A2 = Cptr + k + (long long)(k + ib) * n;
// W = V^T A2
CUBLAS_CHECK(cublasSgemmStridedBatched(
handle,
CUBLAS_OP_T, CUBLAS_OP_N,
ib, nt, m,
&one,
Vptr, m, strideV,
A2, n, strideCmat,
&zero,
Wptr, ib, strideW,
B));
// W2 = T^T W, because trailing update applies H_panel^T.
CUBLAS_CHECK(cublasSgemmStridedBatched(
handle,
CUBLAS_OP_T, CUBLAS_OP_N,
ib, nt, ib,
&one,
Tptr, ib, strideT,
Wptr, ib, strideW,
&zero,
W2ptr, ib, strideW,
B));
// A2 -= V W2
CUBLAS_CHECK(cublasSgemmStridedBatched(
handle,
CUBLAS_OP_N, CUBLAS_OP_N,
m, nt, ib,
&minus_one,
Vptr, m, strideV,
W2ptr, ib, strideW,
&one,
A2, n, strideCmat,
B));
}
}
CUBLAS_CHECK(cublasSetMathMode(handle, old_math));
CUBLAS_CHECK(cublasSetPointerMode(handle, old_ptr));
from_col_major_kernel<<<blocks, threads, 0, stream>>>(
C.data_ptr<float>(), H.data_ptr<float>(), total, n);
C10_CUDA_KERNEL_LAUNCH_CHECK();
return {H, tau};
}
PYBIND11_MODULE(TORCH_EXTENSION_NAME, m) {
m.def("qr_forward", &qr_forward, "Batched square compact Householder QR");
}
"""
def _load_qr_mod():
global _qr_mod
if _qr_mod is None:
if torch.cuda.is_available() and "TORCH_CUDA_ARCH_LIST" not in os.environ:
major, minor = torch.cuda.get_device_capability()
os.environ["TORCH_CUDA_ARCH_LIST"] = f"{major}.{minor}"
_qr_mod = load_inline(
name="b200_compact_householder_qr_ext",
cpp_sources="",
cuda_sources=CUDA_SRC,
functions=None,
with_cuda=True,
extra_cuda_cflags=[
"-O3",
"--ptxas-options=-O3",
],
extra_ldflags=["-lcublas"],
verbose=False,
)
return _qr_mod
def solve(A: torch.Tensor):
return _load_qr_mod().qr_forward(A)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment