Step-by-step tutorial for adding a new lightweight JIT CUDA kernel to sglang.kernels JIT infrastructure and public operator groups
日本語の概要は準備中です。原文の説明を表示しています。
Step-by-step tutorial for adding a heavyweight AOT CUDA/C++ kernel to sgl-kernel (including tests & benchmarks)
インストールする前に、エージェントに与えられる指示の中身を確認できます。
sgl-kernel (AOT / Heavyweight)Apply kernel-organization for the public operator namespace, logical grouping, lazy registry metadata, and test placement. The implementation tutorial below does not replace that API contract.
This tutorial walks through adding a simple element-wise scale operation as an AOT kernel. We'll implement scale(x, factor) = x * factor to demonstrate the complete workflow.
Add a new operation that scales each element of a tensor by a scalar factor:
x (CUDA) and scalar factor (float)x * factor (element-wise, in-place or into pre-allocated out)torch.float16), BF16 (torch.bfloat16), FP32 (torch.float32)
DISPATCH_PYTORCH_DTYPE_TO_CTYPE_FLOAT_FP16 macro (defined in python/sglang/kernels/aot/include/utils.h)python/sglang/kernels/jit first when the kernel does not depend on CUTLASS or another large C++ project. This is the default path for lightweight kernels that benefit from rapid iteration.sgl-kernel when the kernel does depend on CUTLASS or another large C++ project, or when it should be part of the AOT wheel / torch op registration flow.flashinfer, or CUTLASS that is already provided through flashinfer, the kernel can still be implemented as jit_kernel.In addition, every new kernel must ship with:
marker.do_bench from sglang.kernels.jit.benchmark — it works for any callable, not only JIT kernels)You will typically touch these files/areas:
python/sglang/kernels/aot/csrc/elementwise/scale.cu (pick the right subdirectory)python/sglang/kernels/aot/include/sgl_kernel_ops.hpython/sglang/kernels/aot/csrc/common_extension.ccpython/sglang/kernels/aot/CMakeLists.txt (set(SOURCES ...))python/sglang/kernels/aot/python/sgl_kernel/ and python/sglang/kernels/aot/python/sgl_kernel/__init__.pypython/sglang/kernels/aot/tests/test_scale.pypython/sglang/kernels/aot/benchmark/bench_scale.pycsrc/Pick the right subdirectory:
csrc/elementwise/ — for element-wise ops (our example)csrc/gemm/, csrc/attention/, csrc/moe/ — for other categoriesCreate python/sglang/kernels/aot/csrc/elementwise/scale.cu:
#include <ATen/cuda/CUDAContext.h>
#include <c10/cuda/CUDAGuard.h>
#include <torch/all.h>
#include "utils.h" // DISPATCH_PYTORCH_DTYPE_TO_CTYPE_FLOAT_FP16
// scale_kernel: out[i] = input[i] * factor
// Supports float, half (__half), __nv_bfloat16 via template T
template <typename T>
__global__ void scale_kernel(T* __restrict__ out,
const T* __restrict__ input,
float factor,
int64_t n) {
int64_t idx = static_cast<int64_t>(blockIdx.x) * blockDim.x + threadIdx.x;
if (idx < n) {
out[idx] = static_cast<T>(static_cast<float>(input[idx]) * factor);
}
}
void scale(at::Tensor& out, const at::Tensor& input, double factor) {
TORCH_CHECK(input.is_cuda(), "input must be a CUDA tensor");
TORCH_CHECK(input.is_contiguous(), "input must be contiguous");
TORCH_CHECK(out.is_cuda(), "out must be a CUDA tensor");
TORCH_CHECK(out.is_contiguous(), "out must be contiguous");
TORCH_CHECK(out.sizes() == input.sizes(), "out and input must have the same shape");
TORCH_CHECK(out.scalar_type() == input.scalar_type(),
"out and input must have the same dtype");
const int64_t n = input.numel();
const int threads = 256;
const int blocks = (n + threads - 1) / threads;
const cudaStream_t stream = at::cuda::getCurrentCUDAStream();
const at::cuda::OptionalCUDAGuard device_guard(device_of(input));
// Dispatches over float, float16, bfloat16
DISPATCH_PYTORCH_DTYPE_TO_CTYPE_FLOAT_FP16(input.scalar_type(), c_type, [&] {
scale_kernel<c_type><<<blocks, threads, 0, stream>>>(
static_cast<c_type*>(out.data_ptr()),
static_cast<const c_type*>(input.data_ptr()),
static_cast<float>(factor),
n);
cudaError_t status = cudaGetLastError();
TORCH_CHECK(status == cudaSuccess,
"scale_kernel launch failed: ", cudaGetErrorString(status));
return true;
});
}
Key points:
at::Tensor (PyTorch tensors), TORCH_CHECK for validation, at::cuda::getCurrentCUDAStream() for streamDISPATCH_PYTORCH_DTYPE_TO_CTYPE_FLOAT_FP16 covers float, half (FP16), __nv_bfloat16 (BF16)TORCH_CHECK and skip logic in testsinclude/sgl_kernel_ops.hEdit python/sglang/kernels/aot/include/sgl_kernel_ops.h, add to the elementwise section:
void scale(at::Tensor& out, const at::Tensor& input, double factor);
csrc/common_extension.ccEdit python/sglang/kernels/aot/csrc/common_extension.cc, inside TORCH_LIBRARY_FRAGMENT(sgl_kernel, m):
// From csrc/elementwise
m.def("scale(Tensor! out, Tensor input, float factor) -> ()");
m.impl("scale", torch::kCUDA, &scale);
Key points:
Tensor! means in-place / mutable output argumenttorch.compile and for consistent call signaturesfloat here), but note that the C++ launcher signature still needs double for scalar arguments accepted by torch::LibraryCMakeLists.txtEdit python/sglang/kernels/aot/CMakeLists.txt, add to set(SOURCES ...):
csrc/elementwise/scale.cu
Key points:
python/sglang/kernels/aot/python/sgl_kernel/Prefer following the existing module organization first. For elementwise kernels, the usual pattern is:
python/sglang/kernels/aot/python/sgl_kernel/elementwise.pypython/sglang/kernels/aot/python/sgl_kernel/__init__.pyFor example, in python/sglang/kernels/aot/python/sgl_kernel/elementwise.py, add:
import torch
def scale(
input: torch.Tensor,
factor: float,
out: torch.Tensor | None = None,
) -> torch.Tensor:
"""
Element-wise scale: out = input * factor.
Supported dtypes: torch.float16, torch.bfloat16, torch.float32.
Parameters
----------
input : CUDA input tensor
factor : scale factor (float)
out : optional pre-allocated CUDA output tensor (same shape/dtype as input)
"""
if out is None:
out = torch.empty_like(input)
torch.ops.sgl_kernel.scale.default(out, input, factor)
return out
Then re-export it from python/sglang/kernels/aot/python/sgl_kernel/__init__.py following the existing import style used by other kernels.
After exposing the AOT wheel symbol, add a lazy wrapper and a KernelSpec under
python/sglang/kernels/ops/<group>/. Set backend=KernelBackend.AOT, record
actual device/architecture support, and keep sgl_kernel imports inside the
implementation path. SGLang runtime and integration tests import that wrapper.
For this example, use sglang.kernels.ops.elementwise.scale.
The wheel-level tests below validate its standalone API/build. They do not
replace CI-registered SGLang correctness tests in
test/registered/kernels/ops/elementwise/test_scale.py and benchmarks in
test/registered/kernels/benchmark/elementwise/bench_scale.py. Follow
write-sglang-test for their registration and CI budget; do not register tests
under the shipped python/sglang/ package.
Create python/sglang/kernels/aot/tests/test_scale.py:
import pytest
import torch
import sgl_kernel
@pytest.mark.parametrize("dtype", [torch.float16, torch.bfloat16, torch.float32])
@pytest.mark.parametrize("size", [128, 1024, 4096, 65536])
@pytest.mark.parametrize("factor", [0.5, 1.0, 2.0])
def test_scale_correctness(dtype, size, factor):
input = torch.randn(size, dtype=dtype, device="cuda")
out = torch.empty_like(input)
result = sgl_kernel.scale(input, factor, out=out)
assert result is out
expected = input * factor
rtol, atol = (1e-5, 1e-6) if dtype == torch.float32 else (1e-2, 1e-2)
torch.testing.assert_close(out, expected, rtol=rtol, atol=atol)
def test_scale_shape_mismatch():
input = torch.randn(128, dtype=torch.float16, device="cuda")
out = torch.empty(256, dtype=torch.float16, device="cuda")
with pytest.raises(RuntimeError, match="same shape"):
sgl_kernel.scale(input, 2.0, out=out)
def test_scale_cpu_input():
input = torch.randn(128, dtype=torch.float16) # CPU
out = torch.empty_like(input)
with pytest.raises(RuntimeError, match="CUDA"):
sgl_kernel.scale(input, 2.0, out=out)
if __name__ == "__main__":
import sys
sys.exit(pytest.main([__file__, "-q"]))
Every benchmark must account for L2 cache reuse — see rules/kernel-benchmark.md.
Create python/sglang/kernels/aot/benchmark/bench_scale.py:
import torch
import sgl_kernel
from sglang.kernels.jit.benchmark import marker
def sglang_scale(input: torch.Tensor, factor: float, out: torch.Tensor) -> None:
sgl_kernel.scale(input, factor, out=out)
def torch_scale(input: torch.Tensor, factor: float, out: torch.Tensor) -> None:
torch.mul(input, factor, out=out)
FN_MAP = {"sglang": sglang_scale, "torch": torch_scale}
@marker.parametrize("dtype", [torch.float16, torch.bfloat16, torch.float32], [torch.float16])
@marker.parametrize("size", [2**n for n in range(10, 20)], [4096]) # 1K .. 512K
@marker.benchmark("provider", ["sglang", "torch"])
def benchmark(dtype: torch.dtype, size: int, provider: str):
input = torch.randn(size, dtype=dtype, device="cuda")
out = torch.empty_like(input)
return marker.do_bench(
FN_MAP[provider],
# Pass every tensor through input_args (not a closure) so marker rotates
# them across CUDA-graph calls to defeat L2 reuse.
input_args=(input, 2.0, out),
# Bandwidth = bytes(input) + bytes(out), both already in input_args.
memory_output=None,
)
if __name__ == "__main__":
benchmark.run()
Build:
cd python/sglang/kernels/aot
make build -j16
If you need to limit host resource usage:
cd python/sglang/kernels/aot
make build -j1 MAX_JOBS=2 CMAKE_ARGS="-DSGL_KERNEL_COMPILE_THREADS=1"
After building successfully, run the test and benchmark:
pytest python/sglang/kernels/aot/tests/test_scale.py -q
python python/sglang/kernels/aot/benchmark/bench_scale.py
PR CI also runs pr-test-sgl-kernel.yml, including the B200 job
sgl-kernel-b200-test when kernel changes are detected. Use that job as the
Blackwell coverage signal for AOT sgl-kernel changes.
CUDA_LAUNCH_BLOCKING=1compute-sanitizer --tool memcheck python ...MAX_JOBS and SGL_KERNEL_COMPILE_THREADSpython/sglang/kernels/aot/analyze_whl_kernel_sizes.py.cu file is missing from SOURCES, the symbol will be undefined at link timepython/sglang/kernels/aot/README.mdpython/sglang/kernels/aot/include/sgl_kernel_ops.hpython/sglang/kernels/aot/csrc/common_extension.ccpython/sglang/kernels/aot/CMakeLists.txtpython/sglang/kernels/aot/include/utils.h — DISPATCH_PYTORCH_DTYPE_TO_CTYPE_FLOAT_FP16 macro and friendspython/sglang/kernels/aot/csrc/elementwise/activation.cu — reference for the FP16/BF16/FP32 dispatch patternpython/sglang/kernels/aot/csrc/elementwise/scale.cu # NEW: CUDA kernel + launcher
python/sglang/kernels/aot/include/sgl_kernel_ops.h # MODIFIED: C++ declaration
python/sglang/kernels/aot/csrc/common_extension.cc # MODIFIED: schema + dispatch registration
python/sglang/kernels/aot/CMakeLists.txt # MODIFIED: add source file (alphabetical)
python/sglang/kernels/aot/python/sgl_kernel/elementwise.py # MODIFIED: Python wrapper
python/sglang/kernels/aot/python/sgl_kernel/__init__.py # MODIFIED: re-export Python API
python/sglang/kernels/aot/tests/test_scale.py # NEW: tests
python/sglang/kernels/aot/benchmark/bench_scale.py # NEW: benchmark
まだレビューはありません。使ってみた感想をお寄せください。
概要と使いどころ
Step-by-step tutorial for adding a new lightweight JIT CUDA kernel to sglang.kernels JIT infrastructure and public operator groups
日本語の概要は準備中です。原文の説明を表示しています。
Start and persistently pursue a goal to babysit an SGLang pull request until selected GitHub Actions workflows pass on the latest PR head. Use when asked to monitor, babysit, retry, or fix PR CI for lint.yml, pr-test.yml, pr-test-extra.yml, AMD, or other named workflows; classify failures as PR-related versus flaky or infrastructural, auto-fix and push only small clean fixes, rerun failed jobs only up to 10 times, and ignore unselected workflows.
日本語の概要は準備中です。原文の説明を表示しています。
Audit the existing test tree and CI configuration for improvements, using a catalog of patterns previously applied in this repo. Use when asked to audit tests or CI, shrink CI time or cost, find redundant or misplaced tests, clean up a test group, or review whether a CI change follows established practice.
日本語の概要は準備中です。原文の説明を表示しています。
Guide to SGLang CI workflow orchestration — stage ordering, fail-fast, gating, partitioning, execution modes, and debugging CI failures. Use when modifying CI workflows, adding stages, debugging CI pipeline issues, or understanding how tests are dispatched and gated across stages.
日本語の概要は準備中です。原文の説明を表示しています。
Audit SGLang startup logs, save evidence, and propose cleanup for user review. With no arguments, run Qwen3-8B at TP1 and TP2 plus gpt-oss-20b at TP1.
日本語の概要は準備中です。原文の説明を表示しています。
Compute the optimal --mamba-full-memory-ratio (or --max-mamba-cache-size pin) for a hybrid attention + linear-attention (Mamba / GDN / KDA) model's two serving memory pools, from the workload and serving config. Use when a user asks what ratio to set, why concurrency is clamped, or how to size the state vs KV pools for a hybrid model.
日本語の概要は準備中です。原文の説明を表示しています。