submission 851285
vatsalnar · python · License unknown
Use it
Vendorable · source mirrored · license unknownView source →
No package. Vendor the mirrored source: 165 lines, June 9 Researcher Reciprocity License v1.0.
submission.py
curl "https://kernelindex.com/api/v1/implementations/kernelbot-eigh-851285?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:2d2a6fcea8c8effc8d87d84d1c2a96af7354ce8230fd33c532efd3c2536946f7
license declaredunknown
license concludedunknown
authorsvatsalnar
imported2026-08-26
Kernel source
submission.py165 lines
"""Batched eigh via cuSOLVER's cusolverDnXsyevBatched (CUDA >= 12.6).
This is the batched, 64-bit-API version of syevd: LAPACK-quality QR/DC
reduction, one call for the entire batch — the kernel PyTorch's eigh loop
never uses (it calls syevd per matrix). Bound straight out of libcusolver
with ctypes; no compilation, nothing outside the standard environment.
Column-major trick: a row-major symmetric matrix reinterpreted column-major
is itself, so the input transfers as-is; the eigenvector output comes back
transposed, so Q = out.transpose(-1, -2). Eigenvalues are ascending, matching
torch.linalg.eigh.
Fallbacks: torch.linalg.eigh for CPU tensors, n <= 32 (torch already batches
those), missing symbol (older CUDA), nonzero status/info. Input never mutated.
"""
import ctypes
import glob
import os
import torch
from task import input_t, output_t
_DIRECT_N = 32
_CUSOLVER_EIG_MODE_VECTOR = 1
_CUBLAS_FILL_MODE_LOWER = 0
_CUDA_R_32F = 0
def _find_cusolver() -> ctypes.CDLL | None:
candidates: list[str] = []
try:
import nvidia.cusolver
candidates += glob.glob(
os.path.join(os.path.dirname(nvidia.cusolver.__file__), "lib", "libcusolver.so*")
)
except Exception:
pass
torch_dir = os.path.dirname(torch.__file__)
candidates += glob.glob(os.path.join(torch_dir, "lib", "libcusolver*"))
candidates += glob.glob(
os.path.join(os.path.dirname(torch_dir), "nvidia", "cusolver", "lib", "libcusolver.so*")
)
candidates += ["libcusolver.so.12", "libcusolver.so.13", "libcusolver.so.11", "libcusolver.so"]
for path in candidates:
try:
return ctypes.CDLL(path)
except OSError:
continue
return None
class _XsyevBatched:
def __init__(self) -> None:
lib = _find_cusolver()
if lib is None:
raise OSError("libcusolver not found")
self.lib = lib
p, i32, i64, sz = ctypes.c_void_p, ctypes.c_int, ctypes.c_int64, ctypes.c_size_t
lib.cusolverDnCreate.argtypes = [ctypes.POINTER(p)]
lib.cusolverDnCreateParams.argtypes = [ctypes.POINTER(p)]
lib.cusolverDnXsyevBatched_bufferSize.argtypes = [
p, p, i32, i32, i64, i32, p, i64, i32, p, i32,
ctypes.POINTER(sz), ctypes.POINTER(sz), i64,
]
lib.cusolverDnXsyevBatched.argtypes = [
p, p, i32, i32, i64, i32, p, i64, i32, p, i32,
p, sz, p, sz, p, i64,
]
self.handle = p()
self._check(lib.cusolverDnCreate(ctypes.byref(self.handle)), "create")
self.params = p()
self._check(lib.cusolverDnCreateParams(ctypes.byref(self.params)), "params")
self._dev_work: dict[tuple[int, int], torch.Tensor] = {}
self._host_work: dict[tuple[int, int], ctypes.Array] = {}
@staticmethod
def _check(status: int, what: str) -> None:
if status != 0:
raise RuntimeError(f"cusolver {what} failed: status {status}")
def __call__(self, a: torch.Tensor) -> tuple[torch.Tensor, torch.Tensor]:
"""a: (batch, n, n) fp32 contiguous CUDA tensor, overwritten with
eigenvectors (column-major). Returns (transposed vectors, values)."""
lib = self.lib
batch, n, _ = a.shape
w = torch.empty((batch, n), device=a.device, dtype=torch.float32)
info = torch.zeros(batch, device=a.device, dtype=torch.int32)
key = (n, batch)
dev_work = self._dev_work.get(key)
host_work = self._host_work.get(key)
if dev_work is None:
dev_bytes = ctypes.c_size_t(0)
host_bytes = ctypes.c_size_t(0)
self._check(
lib.cusolverDnXsyevBatched_bufferSize(
self.handle, self.params,
_CUSOLVER_EIG_MODE_VECTOR, _CUBLAS_FILL_MODE_LOWER,
n, _CUDA_R_32F, ctypes.c_void_p(a.data_ptr()), n,
_CUDA_R_32F, ctypes.c_void_p(w.data_ptr()), _CUDA_R_32F,
ctypes.byref(dev_bytes), ctypes.byref(host_bytes), batch,
),
"bufferSize",
)
dev_work = torch.empty(
max(int(dev_bytes.value), 16), device=a.device, dtype=torch.uint8
)
host_work = ctypes.create_string_buffer(max(int(host_bytes.value), 16))
self._dev_work[key] = dev_work
self._host_work[key] = host_work
self._check(
lib.cusolverDnXsyevBatched(
self.handle, self.params,
_CUSOLVER_EIG_MODE_VECTOR, _CUBLAS_FILL_MODE_LOWER,
n, _CUDA_R_32F, ctypes.c_void_p(a.data_ptr()), n,
_CUDA_R_32F, ctypes.c_void_p(w.data_ptr()), _CUDA_R_32F,
ctypes.c_void_p(dev_work.data_ptr()), dev_work.numel(),
ctypes.cast(host_work, ctypes.c_void_p), len(host_work),
ctypes.c_void_p(info.data_ptr()), batch,
),
"XsyevBatched",
)
if bool((info != 0).any()):
raise RuntimeError("XsyevBatched: nonzero info")
return a.transpose(-1, -2), w
_solver: _XsyevBatched | None = None
_solver_failed = False
def _get_solver() -> _XsyevBatched | None:
global _solver, _solver_failed
if _solver is None and not _solver_failed:
try:
_solver = _XsyevBatched()
except Exception:
_solver_failed = True
return _solver
def custom_kernel(data: input_t) -> output_t:
batch, n, _ = data.shape
solver = _get_solver() if data.is_cuda and n > _DIRECT_N else None
if solver is None:
values, vectors = torch.linalg.eigh(data)
return vectors, values
scale = data.abs().amax(dim=(-2, -1), keepdim=True)
scale = scale.clamp_min(torch.finfo(torch.float32).tiny)
aw = (data / scale).contiguous()
try:
q, w = solver(aw)
except Exception:
values, vectors = torch.linalg.eigh(data)
return vectors, values
return q, w * scale.squeeze(-1)
scrolls · 165 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