94057c3d3e
PR Test (NPU) / check-changes (push) Has been cancelled
PR Test (NPU) / pr-gate (push) Has been cancelled
PR Test (NPU) / set-image-config (push) Has been cancelled
PR Test (NPU) / stage-b-test-1-npu-a2 (0) (push) Has been cancelled
PR Test (NPU) / stage-b-test-1-npu-a2 (1) (push) Has been cancelled
PR Test (NPU) / stage-b-test-2-npu-a2 (0) (push) Has been cancelled
PR Test (NPU) / stage-b-test-2-npu-a2 (1) (push) Has been cancelled
PR Test (NPU) / stage-b-test-4-npu-a3 (push) Has been cancelled
PR Test (NPU) / stage-b-test-16-npu-a3 (push) Has been cancelled
PR Test (NPU) / multimodal-gen-test-1-npu-a3 (push) Has been cancelled
PR Test (NPU) / multimodal-gen-test-2-npu-a3 (push) Has been cancelled
PR Test (Arm64) / pr-gate (push) Has been cancelled
PR Test (Arm64) / check-changes (push) Has been cancelled
PR Test (Arm64) / build-test (push) Has been cancelled
PR Test (sgl-router) / gate (push) Has been cancelled
PR Test (sgl-router) / tier-1 — lint (push) Has been cancelled
PR Test (sgl-router) / tier-2 — build + test (push) Has been cancelled
PR Test (sgl-router) / tier-3 — docker (placeholder) (push) Has been cancelled
PR Test (sgl-router) / tier-3 — k8s integration (push) Has been cancelled
PR Test (sgl-router) / tier-3 — e2e (push) Has been cancelled
PR Test (sgl-router) / finish (push) Has been cancelled
PR Test (NPU) / single-node-poc (map[name:qwen3_6_27b_w8a8_1p_in64k_out1k_50ms runner:linux-aarch64-a3-2 test_case:test/registered/ascend/performance/qwen3_6_27b/test_npu_qwen3_6_27b_w8a8_1p_in64k_out1k_50ms.py test_type:perf]) (push) Has been cancelled
PR Test (NPU) / pr-test-npu-finish (push) Has been cancelled
PR Test (Xeon) / pr-gate (push) Has been cancelled
PR Test (Xeon) / check-changes (push) Has been cancelled
PR Test (Xeon) / build-test (, xeon-gnr, base-b-test-cpu) (push) Has been cancelled
PR Test (XPU) / check-changes (push) Has been cancelled
PR Test (XPU) / pr-gate (push) Has been cancelled
PR Test (XPU) / stage-a-test-1-gpu-xpu (push) Has been cancelled
PR Test (XPU) / wait-for-stage-a (push) Has been cancelled
PR Test (XPU) / stage-b-test-1-gpu-xpu (push) Has been cancelled
PR Test (XPU) / finish (push) Has been cancelled
CI Model Inventory / build-inventory (push) Has been cancelled
Lint / lint (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark Compilation Check (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark - Manual Policy (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark - Request Processing (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark Summary (push) Has been cancelled
PR Test (SMG) / build-wheel (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on windows (x86_64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on macos (x86_64 - auto) (push) Has been cancelled
PR Test (SMG) / python-unit-tests (push) Has been cancelled
PR Test (SMG) / unit-tests (push) Has been cancelled
PR Test (SMG) / benchmarks (push) Has been cancelled
PR Test (SMG) / chat-completions (push) Has been cancelled
PR Test (SMG) / chat-completions-4gpu (push) Has been cancelled
PR Test (SMG) / e2e (push) Has been cancelled
PR Test (SMG) / docker-build-test (push) Has been cancelled
PR Test (SMG) / k8s-integration (push) Has been cancelled
PR Test (SMG) / finish (push) Has been cancelled
PR Test (SMG) / summarize-benchmarks (push) Has been cancelled
Release SGLang Model Gateway Docker Image / publish (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on macos (aarch64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (aarch64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (x86_64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (aarch64 - musllinux_1_1) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (x86_64 - musllinux_1_1) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / Build SDist (push) Has been cancelled
Release SGLang Model Gateway to PyPI / Upload to PyPI (push) Has been cancelled
Release SGLang Kernels / build-cu129-matrix (aarch64, 12.9, 3.10, arm-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / build-cu129-matrix (x86_64, 12.9, 3.10, x64-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / release-cu129 (push) Has been cancelled
Release SGLang Kernels / build-cu130-matrix (aarch64, 13.0, 3.10, arm-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / build-cu130-matrix (x86_64, 13.0, 3.10, x64-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / release-cu130 (push) Has been cancelled
Release SGLang Kernels / build-rocm-matrix (3.10, 700) (push) Has been cancelled
Release SGLang Kernels / build-rocm-matrix (3.10, 720) (push) Has been cancelled
Release SGLang Kernels / release-rocm700 (push) Has been cancelled
Release SGLang Kernels / release-rocm720 (push) Has been cancelled
Release SGLang Kernels / build-musa43 (43, 3.10) (push) Has been cancelled
Release SGLang Kernels / release-musa43 (push) Has been cancelled
276 lines
9.1 KiB
Plaintext
276 lines
9.1 KiB
Plaintext
// Single-SM Interior Point Method (IPM) LP solver.
|
|
//
|
|
// Solves min c^T x subject to A x = b, x >= 0 with a barrier method:
|
|
// for step in 0..NUM_ITERS:
|
|
// ax2 = A * x^2
|
|
// ax2a = ax2 @ A^T (cuBLASDx GEMM)
|
|
// ax2c = ax2 @ c (cuBLASDx GEMM)
|
|
// d = solve(ax2a, ax2c) (cuSolverDx Cholesky/POSV)
|
|
// r = d^T @ A
|
|
// d = x * (c - r)
|
|
// x *= 1 - 0.999 * d / max(d)
|
|
//
|
|
// Convergence is checked at the end and the kernel writes 0.5 to every
|
|
// element on non-convergence (matches the historical Numba behavior).
|
|
//
|
|
// Adapted from DeepSeek-AI/LPLB's `minilp.cu`. Templated on (NC, NV,
|
|
// BLOCK_DIM, SM_VER, NUM_ITERS) so each unique shape is compiled once via
|
|
// sglang's tvm-ffi load_jit cache.
|
|
|
|
#include <sgl_kernel/tensor.h>
|
|
#include <sgl_kernel/utils.h>
|
|
|
|
#include <sgl_kernel/utils.cuh>
|
|
|
|
#include <dlpack/dlpack.h>
|
|
#include <tvm/ffi/container/tensor.h>
|
|
|
|
#include <cstdint>
|
|
#include <cublasdx.hpp>
|
|
|
|
namespace {
|
|
|
|
template <int NC, int NV>
|
|
struct ipm_smem {
|
|
float b[NC];
|
|
float a[NC][NV];
|
|
float c[NV];
|
|
float ax2[NC][NV];
|
|
float ax2a[NC][NC];
|
|
float x[NV];
|
|
float ax2c[NC];
|
|
float r[NV];
|
|
float d[NV];
|
|
float alpha;
|
|
bool avail_flag;
|
|
};
|
|
|
|
// In-place Cholesky factorization a = L L^T (lower triangle), no external
|
|
// linkage. Replaces cuSolverDx::posv to keep the kernel self-contained
|
|
// under sglang's tvm-ffi load_jit (which uses plain c++ for the final
|
|
// link step and so cannot satisfy cuSolverDx's device-link requirement).
|
|
//
|
|
// For the typical LPLB shape (N <= 32) the algorithm is dwarfed by the
|
|
// cuBLASDx GEMMs.
|
|
template <int N, int BLOCK_DIM>
|
|
__device__ __forceinline__ void cholesky_factor(float a[N][N]) {
|
|
const int tid = threadIdx.x;
|
|
for (int k = 0; k < N; k++) {
|
|
if (tid == 0) {
|
|
// Clamp the pivot away from zero before sqrtf. Numerical drift in
|
|
// the IPM iterations can push a[k][k] slightly negative on
|
|
// otherwise-PSD matrices, which would produce NaN and propagate
|
|
// through the rest of the solve. The convergence check at the end
|
|
// of the kernel writes 0.5 on non-convergence regardless.
|
|
a[k][k] = sqrtf(fmaxf(a[k][k], 1e-12f));
|
|
}
|
|
__syncthreads();
|
|
const float pivot = a[k][k];
|
|
for (int i = k + 1 + tid; i < N; i += BLOCK_DIM) {
|
|
a[i][k] /= pivot;
|
|
}
|
|
__syncthreads();
|
|
// Schur complement: a[i][j] -= a[i][k] * a[j][k] for j>k, i>=j
|
|
for (int idx = tid; idx < N * N; idx += BLOCK_DIM) {
|
|
const int i = idx / N, j = idx % N;
|
|
if (j > k && i >= j && i < N) {
|
|
a[i][j] -= a[i][k] * a[j][k];
|
|
}
|
|
}
|
|
__syncthreads();
|
|
}
|
|
}
|
|
|
|
// Solve L L^T x = b in-place on b, where L is the lower triangle of a
|
|
// (filled by `cholesky_factor`). Forward then back substitution; both
|
|
// run on a single thread because N is small and the inner loops have
|
|
// loop-carried dependencies that don't parallelize cheaply.
|
|
template <int N, int BLOCK_DIM>
|
|
__device__ __forceinline__ void cholesky_apply(const float a[N][N], float b[N]) {
|
|
const int tid = threadIdx.x;
|
|
if (tid == 0) {
|
|
for (int i = 0; i < N; i++) {
|
|
float s = b[i];
|
|
for (int j = 0; j < i; j++) {
|
|
s -= a[i][j] * b[j];
|
|
}
|
|
b[i] = s / a[i][i];
|
|
}
|
|
for (int i = N - 1; i >= 0; i--) {
|
|
float s = b[i];
|
|
for (int j = i + 1; j < N; j++) {
|
|
s -= a[j][i] * b[j];
|
|
}
|
|
b[i] = s / a[i][i];
|
|
}
|
|
}
|
|
__syncthreads();
|
|
}
|
|
|
|
template <int N, int SM_VER, int BLOCK_DIM>
|
|
__device__ __forceinline__ void cholesky_solve(float a[N][N], float b[N]) {
|
|
cholesky_factor<N, BLOCK_DIM>(a);
|
|
cholesky_apply<N, BLOCK_DIM>(a, b);
|
|
}
|
|
|
|
template <int M, int N, int K, int SM_VER, int BLOCK_DIM>
|
|
__device__ __forceinline__ void matmul_NT(float* a, float* b, float* c) {
|
|
decltype(cublasdx::Size<M, N, K>() + cublasdx::Function<cublasdx::function::MM>() + cublasdx::Arrangement<cublasdx::row_major, cublasdx::col_major>() + cublasdx::SM<SM_VER>() + cublasdx::Block() + cublasdx::BlockDim<BLOCK_DIM>())()
|
|
.execute(1.f, a, b, 0.f, c);
|
|
}
|
|
|
|
template <int M, int N, int K, int SM_VER, int BLOCK_DIM>
|
|
__device__ __forceinline__ void matmul_NN(float* a, float* b, float* c) {
|
|
decltype(cublasdx::Size<M, N, K>() + cublasdx::Function<cublasdx::function::MM>() + cublasdx::Arrangement<cublasdx::row_major, cublasdx::row_major>() + cublasdx::SM<SM_VER>() + cublasdx::Block() + cublasdx::BlockDim<BLOCK_DIM>())()
|
|
.execute(1.f, a, b, 0.f, c);
|
|
}
|
|
|
|
template <int NC, int NV, int BLOCK_DIM, int SM_VER, int NUM_ITERS>
|
|
__global__ void ipm_solve_kernel(
|
|
float* __restrict__ result,
|
|
const float* __restrict__ input_a,
|
|
const float* __restrict__ input_b,
|
|
const float* __restrict__ input_c) {
|
|
using SMem = ipm_smem<NC, NV>;
|
|
extern __shared__ unsigned char raw_smem[];
|
|
SMem* smem = reinterpret_cast<SMem*>(raw_smem);
|
|
|
|
const int tid = threadIdx.x;
|
|
const int dim = blockDim.x;
|
|
|
|
auto& a = smem->a;
|
|
auto& b = smem->b;
|
|
auto& c = smem->c;
|
|
|
|
// Load A, b, c into shared memory (single block, no grid index).
|
|
for (int i = tid; i < NC * NV; i += dim) {
|
|
int ic = i / NV, iv = i % NV;
|
|
a[ic][iv] = input_a[i];
|
|
}
|
|
__syncthreads();
|
|
for (int i = tid; i < NC; i += dim) {
|
|
b[i] = input_b[i];
|
|
}
|
|
__syncthreads();
|
|
for (int i = tid; i < NV; i += dim) {
|
|
c[i] = input_c[i];
|
|
}
|
|
__syncthreads();
|
|
|
|
auto& ax2 = smem->ax2;
|
|
auto& ax2a = smem->ax2a;
|
|
auto& x = smem->x;
|
|
auto& ax2c = smem->ax2c;
|
|
auto& r = smem->r;
|
|
auto& d = smem->d;
|
|
auto& alpha = smem->alpha;
|
|
// d_max and max_residual are warp-scope reductions; only valid for tid<32.
|
|
float d_max = 0.f;
|
|
float max_residual = 0.f;
|
|
|
|
for (int j = tid; j < NV; j += dim) {
|
|
x[j] = 1.f;
|
|
}
|
|
__syncthreads();
|
|
|
|
for (int step = 0; step < NUM_ITERS; step++) {
|
|
for (int ij = tid; ij < NC * NV; ij += dim) {
|
|
int i = ij / NV, j = ij % NV;
|
|
ax2[i][j] = a[i][j] * x[j] * x[j];
|
|
}
|
|
__syncthreads();
|
|
|
|
matmul_NT<NC, NC, NV, SM_VER, BLOCK_DIM>(ax2[0], a[0], ax2a[0]);
|
|
matmul_NT<NC, 1, NV, SM_VER, BLOCK_DIM>(ax2[0], c, ax2c);
|
|
cholesky_solve<NC, SM_VER, BLOCK_DIM>(ax2a, ax2c);
|
|
matmul_NN<1, NV, NC, SM_VER, BLOCK_DIM>(ax2c, a[0], r);
|
|
|
|
if (tid < 32) {
|
|
d_max = 0.f;
|
|
for (int j = tid; j < NV; j += 32) {
|
|
float val = x[j] * (c[j] - r[j]);
|
|
d[j] = val;
|
|
d_max = fmaxf(d_max, val);
|
|
}
|
|
for (int offset = 16; offset > 0; offset >>= 1) {
|
|
d_max = fmaxf(d_max, __shfl_xor_sync(0xffffffff, d_max, offset));
|
|
}
|
|
if (tid == 0) {
|
|
// Guard against d_max <= 0 from a degenerate / numerically-stuck
|
|
// iteration. A non-positive d_max would yield inf/NaN from the
|
|
// division and corrupt x on the next update. The 1.0 fallback
|
|
// produces a no-op step (x *= 1 - 1*0 = x) so the solver simply
|
|
// stalls rather than diverges, and the convergence check at the
|
|
// end of the kernel writes 0.5 if d_max stays small.
|
|
alpha = (d_max > 1e-9f) ? (0.999f / d_max) : 1.0f;
|
|
}
|
|
}
|
|
__syncthreads();
|
|
|
|
for (int j = tid; j < NV; j += dim) {
|
|
x[j] *= 1.f - alpha * d[j];
|
|
}
|
|
__syncthreads();
|
|
}
|
|
|
|
// Compute residual ‖A x - b‖_inf for the convergence check.
|
|
matmul_NT<NC, 1, NV, SM_VER, BLOCK_DIM>(a[0], x, ax2c);
|
|
if (tid < 32) {
|
|
max_residual = 0.f;
|
|
for (int i = tid; i < NC; i += 32) {
|
|
max_residual = fmaxf(max_residual, fabsf(ax2c[i] - b[i]));
|
|
}
|
|
for (int offset = 16; offset > 0; offset >>= 1) {
|
|
max_residual = fmaxf(max_residual, __shfl_down_sync(0xffffffff, max_residual, offset));
|
|
}
|
|
}
|
|
|
|
auto& avail_flag = smem->avail_flag;
|
|
if (tid == 0) {
|
|
avail_flag = (d_max < 0.1f && x[NV - 1] >= 0.f && x[NV - 1] < 1e-4f && max_residual < 0.05f);
|
|
}
|
|
__syncthreads();
|
|
|
|
if (!avail_flag) {
|
|
for (int i = tid; i < NV; i += dim) {
|
|
result[i] = 0.5f;
|
|
}
|
|
} else {
|
|
for (int i = tid; i < NV; i += dim) {
|
|
result[i] = x[i];
|
|
}
|
|
}
|
|
}
|
|
|
|
template <int NC, int NV, int BLOCK_DIM, int SM_VER, int NUM_ITERS>
|
|
void ipm_solve(tvm::ffi::TensorView A, tvm::ffi::TensorView b, tvm::ffi::TensorView c, tvm::ffi::TensorView result) {
|
|
using namespace host;
|
|
|
|
SymbolicDevice device_;
|
|
TensorMatcher({NC, NV}).with_dtype<float>().with_device<kDLCUDA>(device_).verify(A);
|
|
TensorMatcher({NC}).with_dtype<float>().with_device<kDLCUDA>(device_).verify(b);
|
|
TensorMatcher({NV}).with_dtype<float>().with_device<kDLCUDA>(device_).verify(c);
|
|
TensorMatcher({NV}).with_dtype<float>().with_device<kDLCUDA>(device_).verify(result);
|
|
|
|
const DLDevice device = device_.unwrap();
|
|
const size_t smem_bytes = sizeof(ipm_smem<NC, NV>);
|
|
|
|
using KernelT = void (*)(float*, const float*, const float*, const float*);
|
|
KernelT kernel = ipm_solve_kernel<NC, NV, BLOCK_DIM, SM_VER, NUM_ITERS>;
|
|
|
|
// Opt in to >48 KB dynamic shared memory if needed (Hopper supports up to
|
|
// 228 KB per block).
|
|
if (smem_bytes > 48 * 1024) {
|
|
cudaFuncSetAttribute(kernel, cudaFuncAttributeMaxDynamicSharedMemorySize, static_cast<int>(smem_bytes));
|
|
}
|
|
|
|
LaunchKernel(/*grid_dim=*/1, /*block_dim=*/BLOCK_DIM, device, smem_bytes)(
|
|
kernel,
|
|
static_cast<float*>(result.data_ptr()),
|
|
static_cast<const float*>(A.data_ptr()),
|
|
static_cast<const float*>(b.data_ptr()),
|
|
static_cast<const float*>(c.data_ptr()));
|
|
}
|
|
|
|
} // namespace
|