diff --git a/benchmark/kernels/flashinfer_allreduce_fusion/README.md b/benchmark/kernels/flashinfer_allreduce_fusion/README.md
index e651604c7..f1379cb19 100644
--- a/benchmark/kernels/flashinfer_allreduce_fusion/README.md
+++ b/benchmark/kernels/flashinfer_allreduce_fusion/README.md
@@ -97,6 +97,6 @@ If `--output-file` is specified, all configurations will be summarized in Markdo
- The fused operator internally uses "oneshot"/"twoshot" two trigger methods; oneshot is enabled by default and twoshot is tested simultaneously.
- FP8/FP4:
- FP8 uses sglang's FP8 tools and dtype, with underlying platform selection of `e4m3`/`e4m3fnuz` etc.
- - FP4 uses sgl-kernel's `scaled_fp4_quant`, requiring corresponding platform support.
+ - FP4 uses sglang's `fp4_quantize` (FlashInfer-backed), requiring corresponding platform support.
- CUDA Graph:
- Uses sglang's `graph_capture()` to prepare capture-ready state for communication, then uses `torch.cuda.graph` to capture kernels, reducing measurement jitter.
diff --git a/benchmark/kernels/flashinfer_allreduce_fusion/benchmark_fused_collective.py b/benchmark/kernels/flashinfer_allreduce_fusion/benchmark_fused_collective.py
index d1c3f545c..2905a62a1 100644
--- a/benchmark/kernels/flashinfer_allreduce_fusion/benchmark_fused_collective.py
+++ b/benchmark/kernels/flashinfer_allreduce_fusion/benchmark_fused_collective.py
@@ -43,11 +43,11 @@ try:
from sgl_kernel import fused_add_rmsnorm as SGL_FUSED_ADD_RMS_NORM
from sgl_kernel import rmsnorm as SGL_RMS_NORM
- from sglang.jit_kernel.nvfp4 import scaled_fp4_quant as SGL_SCALED_FP4_QUANT
+ from sglang.srt.layers.quantization.fp4_utils import fp4_quantize as SGL_FP4_QUANT
except Exception: # pragma: no cover - fallback on non-supported platforms
SGL_FUSED_ADD_RMS_NORM = None
SGL_RMS_NORM = None
- SGL_SCALED_FP4_QUANT = None
+ SGL_FP4_QUANT = None
FP8_DTYPE = SGLANG_FP8_DTYPE
@@ -386,9 +386,9 @@ def standard_allreduce_rmsnorm_fp4_quant(
residual_out = allreduce_out
# Finally FP4 quantization
- if SGL_SCALED_FP4_QUANT is None:
- raise RuntimeError("scaled_fp4_quant is not available on this platform")
- quant_res, output_scale_res = SGL_SCALED_FP4_QUANT(quant_input, input_global_scale)
+ if SGL_FP4_QUANT is None:
+ raise RuntimeError("fp4_quantize is not available on this platform")
+ quant_res, output_scale_res = SGL_FP4_QUANT(quant_input, input_global_scale)
if residual is not None:
return quant_res, residual_out, output_scale_res
else:
@@ -464,9 +464,9 @@ def standard_allreduce_rmsnorm_fp4_quant_native(
residual_out = allreduce_out
# Apply FP4 quantization (still using fused CUDA op as there's no native FP4)
- if SGL_SCALED_FP4_QUANT is None:
- raise RuntimeError("scaled_fp4_quant is not available on this platform")
- quant_res, output_scale_res = SGL_SCALED_FP4_QUANT(quant_input, input_global_scale)
+ if SGL_FP4_QUANT is None:
+ raise RuntimeError("fp4_quantize is not available on this platform")
+ quant_res, output_scale_res = SGL_FP4_QUANT(quant_input, input_global_scale)
if residual is not None:
return quant_res, residual_out, output_scale_res
diff --git a/benchmark/kernels/quantization/bench_fp4_quant.py b/benchmark/kernels/quantization/bench_fp4_quant.py
deleted file mode 100644
index 0d5b54aeb..000000000
--- a/benchmark/kernels/quantization/bench_fp4_quant.py
+++ /dev/null
@@ -1,137 +0,0 @@
-"""Benchmark FP4 quantize: sglang jit_kernel vs flashinfer.
-
-Compares ``sglang.jit_kernel.nvfp4.scaled_fp4_quant`` against
-``flashinfer.fp4_quantize`` over a sweep of (M, K) shapes.
-
-Timing uses ``flashinfer.testing.bench_gpu_time`` (CUDA-graph based with
-rotating-buffer cold-L2).
-"""
-
-import argparse
-import itertools
-
-import numpy as np
-import torch
-from flashinfer import fp4_quantize as flashinfer_fp4_quantize
-from flashinfer.testing import bench_gpu_time
-
-from sglang.jit_kernel.nvfp4 import scaled_fp4_quant
-
-Ms = [1, 8, 32, 128, 512, 1024, 2048, 4096, 8192, 16384, 32768]
-Ks = [128, 256, 384, 512, 768, 1024, 1536, 2048, 3072, 4096, 5120, 6144, 8192, 16384]
-
-
-def _bench(fn, input_args) -> float:
- times = bench_gpu_time(
- fn=fn,
- input_args=input_args,
- use_cuda_graph=True,
- dry_run_time_ms=25,
- repeat_time_ms=100,
- )
- return float(np.median(times))
-
-
-def benchmark(M: int, K: int, dtype: torch.dtype, device: str):
- x = torch.randn(M, K, device=device, dtype=dtype)
- global_scale = torch.ones(1, device=device, dtype=torch.float32)
-
- sglang_ms = _bench(
- lambda x, gs: scaled_fp4_quant(x, gs),
- input_args=(x, global_scale),
- )
- flashinfer_ms = _bench(
- lambda x, gs: flashinfer_fp4_quantize(x, gs, backend="cute-dsl"),
- input_args=(x, global_scale),
- )
-
- return sglang_ms, flashinfer_ms
-
-
-def plot_speedup(rows, path):
- import matplotlib
-
- matplotlib.use("Agg")
- import matplotlib.pyplot as plt
-
- Ms_unique = sorted({int(r[0]) for r in rows})
- Ks_unique = sorted({int(r[1]) for r in rows})
- grid = np.full((len(Ms_unique), len(Ks_unique)), np.nan)
- m_idx = {m: i for i, m in enumerate(Ms_unique)}
- k_idx = {k: i for i, k in enumerate(Ks_unique)}
- for M, K, _, _, sp in rows:
- grid[m_idx[int(M)], k_idx[int(K)]] = float(sp)
-
- fig, ax = plt.subplots(figsize=(12, 8))
- vmax = max(2.0, np.nanmax(grid))
- vmin = min(0.5, np.nanmin(grid))
- im = ax.imshow(
- grid,
- aspect="auto",
- cmap="RdYlGn",
- vmin=vmin,
- vmax=vmax,
- origin="lower",
- )
- ax.set_xticks(range(len(Ks_unique)))
- ax.set_xticklabels(Ks_unique, rotation=45)
- ax.set_yticks(range(len(Ms_unique)))
- ax.set_yticklabels(Ms_unique)
- ax.set_xlabel("K")
- ax.set_ylabel("M")
- ax.set_title("Speedup: flashinfer / sglang (>1 means sglang faster)")
- for i in range(len(Ms_unique)):
- for j in range(len(Ks_unique)):
- v = grid[i, j]
- if np.isfinite(v):
- ax.text(j, i, f"{v:.2f}", ha="center", va="center", fontsize=7)
- fig.colorbar(im, ax=ax, label="speedup")
- fig.tight_layout()
- fig.savefig(path, dpi=130)
- print(f"Saved plot to {path}")
-
-
-def main():
- parser = argparse.ArgumentParser()
- parser.add_argument("--dtype", choices=["bf16", "fp16"], default="bf16")
- parser.add_argument("--device", default="cuda")
- parser.add_argument("--csv", type=str, default=None)
- parser.add_argument("--plot", type=str, default=None)
- args = parser.parse_args()
-
- dtype = torch.bfloat16 if args.dtype == "bf16" else torch.float16
-
- rows = []
- header = (
- f"{'M':>8} {'K':>8} {'sglang(us)':>12} {'flashinfer(us)':>16} {'speedup':>10}"
- )
- print(header)
- print("-" * len(header))
-
- for M, K in itertools.product(Ms, Ks):
- try:
- sglang_ms, flashinfer_ms = benchmark(M, K, dtype, args.device)
- except Exception as e:
- print(f"{M:>8} {K:>8} skipped: {e}")
- continue
- sglang_us = sglang_ms * 1e3
- flashinfer_us = flashinfer_ms * 1e3
- speedup = flashinfer_us / sglang_us
- print(
- f"{M:>8} {K:>8} {sglang_us:>12.3f} {flashinfer_us:>16.3f} {speedup:>10.3f}"
- )
- rows.append((M, K, sglang_us, flashinfer_us, speedup))
-
- if args.csv:
- with open(args.csv, "w") as f:
- f.write("M,K,sglang_us,flashinfer_us,speedup_flashinfer_over_sglang\n")
- for M, K, s, fi, sp in rows:
- f.write(f"{M},{K},{s:.6f},{fi:.6f},{sp:.6f}\n")
- print(f"Saved CSV to {args.csv}")
-
- if args.plot:
- plot_speedup(rows, args.plot)
-
-
-if __name__ == "__main__":
- main()
diff --git a/docs_new/docs/advanced_features/quantization.mdx b/docs_new/docs/advanced_features/quantization.mdx
index ee0d4b7e9..f913a0d3e 100644
--- a/docs_new/docs/advanced_features/quantization.mdx
+++ b/docs_new/docs/advanced_features/quantization.mdx
@@ -284,11 +284,6 @@ Backend selection applies to **blockwise FP8**, **MXFP8** (dense linear), and **
SM80+ |
Auto-selects: flashinfer_cutedsl on SM100; marlin on SM80-SM90; flashinfer_cutlass otherwise (including SM120) |
-
- cutlass |
- SM100/120 |
- SGLang CUTLASS kernel |
-
flashinfer_cutlass |
SM100/120 |
@@ -317,7 +312,7 @@ Backend selection applies to **blockwise FP8**, **MXFP8** (dense linear), and **
-On Blackwell, when FlashInfer is unavailable for NVFP4, the SGLang CUTLASS kernel is used as an automatic fallback. On SM80-SM90, `auto` selects Marlin for NVFP4.
+On SM80-SM90, `auto` selects Marlin for NVFP4. NVFP4 GEMM requires FlashInfer to be installed.
## Offline Quantization
diff --git a/docs_new/docs/advanced_features/server_arguments.mdx b/docs_new/docs/advanced_features/server_arguments.mdx
index 7a71a1a02..7694eac40 100644
--- a/docs_new/docs/advanced_features/server_arguments.mdx
+++ b/docs_new/docs/advanced_features/server_arguments.mdx
@@ -1436,9 +1436,9 @@ Please consult the documentation below and [server_args.py](https://github.com/s
| `--fp4-gemm-backend` |
- Choose the runner backend for NVFP4 GEMM operations. Options: 'auto' (default; selects flashinfer_cutedsl on SM100, marlin on SM80-SM90, flashinfer_cutlass otherwise (including SM120)), 'cutlass' (SGLang CUTLASS kernel), 'flashinfer_cutlass' (FlashInfer CUTLASS backend), 'flashinfer_cudnn' (FlashInfer cuDNN backend, optimal on CUDA 13+ with cuDNN 9.15+), 'flashinfer_cutedsl' (FlashInfer CuTe DSL backend), 'flashinfer_trtllm' (FlashInfer TensorRT-LLM backend, requires different weight preparation with shuffling), 'marlin' (weight-only W4A16 fallback for SM80-SM90). All FlashInfer backends fall back to sgl-kernel CUTLASS when FlashInfer is unavailable. |
+ Choose the runner backend for NVFP4 GEMM operations. Options: 'auto' (default; selects flashinfer_cutedsl on SM100, marlin on SM80-SM90, flashinfer_cutlass otherwise (including SM120)), 'flashinfer_cutlass' (FlashInfer CUTLASS backend), 'flashinfer_cudnn' (FlashInfer cuDNN backend, optimal on CUDA 13+ with cuDNN 9.15+), 'flashinfer_cutedsl' (FlashInfer CuTe DSL backend), 'flashinfer_trtllm' (FlashInfer TensorRT-LLM backend, requires different weight preparation with shuffling), 'marlin' (weight-only W4A16 fallback for SM80-SM90). Requires FlashInfer to be installed. |
`auto` |
- auto, cutlass, flashinfer_cudnn, flashinfer_cutedsl, flashinfer_cutlass, flashinfer_trtllm, marlin |
+ auto, flashinfer_cudnn, flashinfer_cutedsl, flashinfer_cutlass, flashinfer_trtllm, marlin |
| `--disable-flashinfer-autotune` |
diff --git a/python/sglang/jit_kernel/csrc/gemm/nvfp4/nvfp4_expert_quant.cuh b/python/sglang/jit_kernel/csrc/gemm/nvfp4/nvfp4_expert_quant.cuh
deleted file mode 100644
index 6378825da..000000000
--- a/python/sglang/jit_kernel/csrc/gemm/nvfp4/nvfp4_expert_quant.cuh
+++ /dev/null
@@ -1,806 +0,0 @@
-#include
-#include
-
-#include
-#include
-#include
-
-#include "nvfp4_quant.cuh"
-#include
-#include
-
-using namespace host;
-
-// Quantizes the provided PackedVec into the uint32_t output
-template
-SGL_DEVICE uint32_t cvt_warp_fp16_to_fp4(PackedVec& vec, float SFScaleVal, uint8_t* SFout) {
-#if defined(__CUDA_ARCH__) && (__CUDA_ARCH__ >= 1000)
- // Get absolute maximum values among the local 8 values.
- auto localMax = __habs2(vec.elts[0]);
-
-// Local maximum value.
-#pragma unroll
- for (int i = 1; i < CVT_FP4_ELTS_PER_THREAD / 2; i++) {
- localMax = __hmax2(localMax, __habs2(vec.elts[i]));
- }
-
- // Get the absolute maximum among all 16 values (two threads).
- localMax = __hmax2(__shfl_xor_sync(uint32_t(-1), localMax, 1), localMax);
- // Get the final absolute maximum values.
- float vecMax = float(__hmax(localMax.x, localMax.y));
-
- // Get the SF (max value of the vector / max value of e2m1).
- // maximum value of e2m1 = 6.0.
- // TODO: use half as compute data type.
- float SFValue = SFScaleVal * (vecMax * reciprocal_approximate_ftz(6.0f));
- // 8 bits representation of the SF.
- uint8_t fp8SFVal;
- // Write the SF to global memory (STG.8).
- if constexpr (UE8M0_SF) {
- // Extract the 8 exponent bits from float32.
- // float 32bits = 1 sign bit + 8 exponent bits + 23 mantissa bits.
- uint32_t tmp = reinterpret_cast(SFValue) >> 23;
- fp8SFVal = tmp & 0xff;
- // Convert back to fp32.
- reinterpret_cast(SFValue) = tmp << 23;
- } else {
- // Here SFValue is always positive, so E4M3 is the same as UE4M3.
- __nv_fp8_e4m3 tmp = __nv_fp8_e4m3(SFValue);
- reinterpret_cast<__nv_fp8_e4m3&>(fp8SFVal) = tmp;
- // Convert back to fp32.
- SFValue = float(tmp);
- }
- // Get the output scale.
- // Recipe: final_scale = reciprocal(fp32(fp8(SFValue * SFScaleVal))) *
- // reciprocal(SFScaleVal))
- float outputScale =
- SFValue != 0 ? reciprocal_approximate_ftz(SFValue * reciprocal_approximate_ftz(SFScaleVal)) : 0.0f;
-
- if (SFout) {
- // Write the SF to global memory (STG.8).
- *SFout = fp8SFVal;
- }
-
- // Convert the input to float.
- float2 fp2Vals[CVT_FP4_ELTS_PER_THREAD / 2];
-
-#pragma unroll
- for (int i = 0; i < CVT_FP4_ELTS_PER_THREAD / 2; i++) {
- fp2Vals[i] = device::cast(vec.elts[i]);
- fp2Vals[i].x *= outputScale;
- fp2Vals[i].y *= outputScale;
- }
-
- // Convert to e2m1 values.
- uint32_t e2m1Vec = fp32_vec_to_e2m1(fp2Vals);
-
- // Write the e2m1 values to global memory.
- return e2m1Vec;
-#else
- return 0;
-#endif
-}
-
-SGL_DEVICE float silu(const float& val) {
- return val / (1.0f + __expf(-val));
-}
-
-template
-SGL_DEVICE void silu_and_mul(PackedVec& x_vec, const PackedVec& y_vec) {
- float2 x[CVT_FP4_ELTS_PER_THREAD / 2];
- float2 y[CVT_FP4_ELTS_PER_THREAD / 2];
-
-#pragma unroll
- for (int i = 0; i < CVT_FP4_ELTS_PER_THREAD / 2; i++) {
- x[i] = device::cast(x_vec.elts[i]);
- y[i] = device::cast(y_vec.elts[i]);
- x[i].x = silu(x[i].x) * y[i].x;
- x[i].y = silu(x[i].y) * y[i].y;
- x_vec.elts[i] = device::cast>(x[i]);
- }
-}
-
-// Use UE4M3 by default.
-template
-__global__ void
-#if defined(__CUDA_ARCH__) && (__CUDA_ARCH__ >= 1000)
-__launch_bounds__(512, 4) cvt_fp16_to_fp4(
-#else
-cvt_fp16_to_fp4(
-#endif
- int32_t numRows,
- int32_t numCols,
- Type const* in,
- float const* SFScale,
- uint32_t* out,
- uint32_t* SFout,
- uint32_t* input_offset_by_experts,
- uint32_t* output_scale_offset_by_experts,
- int32_t* mask,
- int n_experts,
- bool low_latency,
- bool use_silu_and_mul) {
-#if defined(__CUDA_ARCH__) && (__CUDA_ARCH__ >= 1000)
- using PackedVec = PackedVec;
- static constexpr int CVT_FP4_NUM_THREADS_PER_SF = (CVT_FP4_SF_VEC_SIZE / CVT_FP4_ELTS_PER_THREAD);
- static_assert(sizeof(PackedVec) == sizeof(Type) * CVT_FP4_ELTS_PER_THREAD, "Vec size is not matched.");
-
- // Input tensor row/col loops.
- int tid = blockIdx.x * blockDim.x + threadIdx.x;
- int colsPerRow = numCols / CVT_FP4_ELTS_PER_THREAD;
- bool use_mask = mask != nullptr;
- // When use_silu_and_mul is true, input last dim is 2*k (gate+up concatenated).
- int actualColsPerRow = (use_mask || use_silu_and_mul) ? colsPerRow * 2 : colsPerRow;
-
- // Each global thread processes one element
- for (int globalIdx = tid; globalIdx < numRows * colsPerRow; globalIdx += gridDim.x * blockDim.x) {
- // Calculate which row and column this global thread should process
- int rowIdx = globalIdx / colsPerRow;
- int colIdx = globalIdx % colsPerRow;
-
- // Find index within the experts using different strategies based on expert
- // count
- int rowIdx_in_expert = 0;
- int expert_idx = 0;
-
- if constexpr (SMALL_NUM_EXPERTS) {
- for (int i = 0; i < n_experts; i++) {
- uint32_t current_offset = __ldca(&input_offset_by_experts[i]);
- uint32_t next_offset = __ldca(&input_offset_by_experts[i + 1]);
- if (rowIdx >= current_offset && rowIdx < next_offset) {
- rowIdx_in_expert = rowIdx - current_offset;
- expert_idx = i;
- break;
- }
- }
- } else {
- // Load input offsets into registers first, then do the computation.
- // Local array size set to 17 because of register limit.
- uint32_t local_offsets[17];
- for (int chunk_start = 0; chunk_start < n_experts; chunk_start += 16) {
- *reinterpret_cast(local_offsets) =
- __ldca(reinterpret_cast(&input_offset_by_experts[chunk_start]));
- *reinterpret_cast(local_offsets + 4) =
- __ldca(reinterpret_cast(&input_offset_by_experts[chunk_start + 4]));
- *reinterpret_cast(local_offsets + 8) =
- __ldca(reinterpret_cast(&input_offset_by_experts[chunk_start + 8]));
- *reinterpret_cast(local_offsets + 12) =
- __ldca(reinterpret_cast(&input_offset_by_experts[chunk_start + 12]));
- local_offsets[16] = __ldca(&input_offset_by_experts[chunk_start + 16]);
-
-// Check against the 16 loaded offsets
-#pragma unroll
- for (int i = 0; i < 16; i++) {
- if (rowIdx >= local_offsets[i] && rowIdx < local_offsets[i + 1]) {
- rowIdx_in_expert = rowIdx - local_offsets[i];
- expert_idx = chunk_start + i;
- break;
- }
- }
- }
- }
-
- // Early exit when using masks.
- if (use_mask && rowIdx_in_expert >= mask[expert_idx]) {
- continue;
- }
-
- int64_t inOffset = rowIdx * actualColsPerRow + colIdx;
- PackedVec in_vec = reinterpret_cast(in)[inOffset];
- if (use_mask || use_silu_and_mul) {
- PackedVec in_vec_mul = reinterpret_cast(in)[inOffset + colsPerRow];
- silu_and_mul(in_vec, in_vec_mul);
- }
-
- // Get the output tensor offset.
- // Same as inOffset because 8 elements are packed into one uint32_t.
- int64_t outOffset = rowIdx * colsPerRow + colIdx;
- auto& out_pos = out[outOffset];
-
- // Get the global scaling factor, which will be applied to the SF.
- // Note SFScale is the same as next GEMM's alpha, which is
- // (448.f / (Alpha_A / 6.f)).
- float const SFScaleVal = SFScale == nullptr ? 1.0f : SFScale[expert_idx];
-
- int factor = CVT_FP4_SF_VEC_SIZE * 4;
- // The actual output_scales dim is computed from the padded numCols.
- int32_t numCols_padded = (numCols + factor - 1) / factor * factor;
- int numCols_SFout = numCols_padded / CVT_FP4_SF_VEC_SIZE / 4;
- uint32_t* SFout_in_expert = SFout + output_scale_offset_by_experts[expert_idx] * numCols_SFout;
-
- auto sf_out = cvt_quant_to_fp4_get_sf_out_offset(
- rowIdx_in_expert, colIdx, numCols, SFout_in_expert);
-
- out_pos = cvt_warp_fp16_to_fp4(in_vec, SFScaleVal, sf_out);
- }
-#endif
-}
-
-// Use UE4M3 by default.
-template
-__global__ void
-#if defined(__CUDA_ARCH__) && (__CUDA_ARCH__ >= 1000)
-__launch_bounds__(512, 4) cvt_fp16_to_fp4_expert(
-#else
-cvt_fp16_to_fp4_expert(
-#endif
- int32_t numRows,
- int32_t numCols,
- Type const* in,
- float const* SFScale,
- uint32_t* out,
- uint32_t* SFout,
- int32_t* mask,
- bool use_silu_and_mul,
- int n_experts) {
-#if defined(__CUDA_ARCH__) && (__CUDA_ARCH__ >= 1000)
- using PackedVec = PackedVec;
- static constexpr int CVT_FP4_NUM_THREADS_PER_SF = (CVT_FP4_SF_VEC_SIZE / CVT_FP4_ELTS_PER_THREAD);
- static_assert(sizeof(PackedVec) == sizeof(Type) * CVT_FP4_ELTS_PER_THREAD, "Vec size is not matched.");
-
- // Input tensor row/col loops.
- int tid = blockIdx.x * blockDim.x + threadIdx.x;
- int stride = (gridDim.x * blockDim.x) / n_experts;
- int remainder = (gridDim.x * blockDim.x) % n_experts;
- int expert_idx;
- int tid_in_expert;
- int actual_stride;
- if (remainder > 0) {
- int bound = remainder * (stride + 1);
- if (tid < bound) {
- expert_idx = tid / (stride + 1);
- tid_in_expert = tid % (stride + 1);
- actual_stride = stride + 1;
- } else {
- expert_idx = remainder + (tid - bound) / stride;
- tid_in_expert = (tid - bound) % stride;
- actual_stride = stride;
- }
- } else {
- expert_idx = tid / stride;
- tid_in_expert = tid % stride;
- actual_stride = stride;
- }
- int m = numRows / n_experts;
- int padded_m = (m + (128 - 1)) / 128 * 128;
-
- int colsPerRow = numCols / CVT_FP4_ELTS_PER_THREAD;
- // TODO(kaixih@nvidia): For now, we assume mask is used together with
- // silu_and_mal. Maybe we want a more general behavior of mask later. In the
- // silu case, the input last dim doubles.
- bool use_mask = mask != nullptr;
- int actualColsPerRow = use_silu_and_mul ? colsPerRow * 2 : colsPerRow;
-
- // Each global thread processes one element
- for (int globalIdx = tid_in_expert + expert_idx * m * colsPerRow; globalIdx < (expert_idx + 1) * m * colsPerRow;
- globalIdx += actual_stride) {
- // Calculate which row and column this global thread should process
- int rowIdx = globalIdx / colsPerRow;
- int colIdx = globalIdx % colsPerRow;
-
- // Find index within the experts
- int rowIdx_in_expert = rowIdx - expert_idx * m;
-
- // Early exit when using masks.
- if (use_mask && rowIdx_in_expert >= mask[expert_idx]) {
- break;
- }
-
- int64_t inOffset = rowIdx * actualColsPerRow + colIdx;
- PackedVec in_vec = reinterpret_cast(in)[inOffset];
- if (use_silu_and_mul) {
- PackedVec in_vec_mul = reinterpret_cast(in)[inOffset + colsPerRow];
- silu_and_mul(in_vec, in_vec_mul);
- }
-
- // Get the output tensor offset.
- // Same as inOffset because 8 elements are packed into one uint32_t.
- int64_t outOffset = rowIdx * colsPerRow + colIdx;
- auto& out_pos = out[outOffset];
-
- // Get the global scaling factor, which will be applied to the SF.
- // Note SFScale is the same as next GEMM's alpha, which is
- // (448.f / (Alpha_A / 6.f)).
- float const SFScaleVal = SFScale == nullptr ? 1.0f : SFScale[expert_idx];
-
- int factor = CVT_FP4_SF_VEC_SIZE * 4;
- // The actual output_scales dim is computed from the padded numCols.
- int32_t numCols_padded = (numCols + factor - 1) / factor * factor;
- int numCols_SFout = numCols_padded / CVT_FP4_SF_VEC_SIZE / 4;
- uint32_t* SFout_in_expert = SFout + expert_idx * padded_m * numCols_SFout;
-
- auto sf_out = cvt_quant_to_fp4_get_sf_out_offset(
- rowIdx_in_expert, colIdx, numCols, SFout_in_expert);
-
- out_pos = cvt_warp_fp16_to_fp4(in_vec, SFScaleVal, sf_out);
- }
-#endif
-}
-
-// Kernel for LARGE_M_TOPK = true (large m_topk optimized version)
-template
-__global__ void
-#if defined(__CUDA_ARCH__) && (__CUDA_ARCH__ >= 1000)
-__launch_bounds__(1024, 4) cvt_fp16_to_fp4(
-#else
-cvt_fp16_to_fp4(
-#endif
- int32_t numRows,
- int32_t numCols,
- Type const* in,
- float const* SFScale,
- uint32_t* out,
- uint32_t* SFout,
- uint32_t* input_offset_by_experts,
- uint32_t* output_scale_offset_by_experts,
- int32_t* mask,
- int n_experts,
- bool use_silu_and_mul) {
-#if defined(__CUDA_ARCH__) && (__CUDA_ARCH__ >= 1000)
- using PackedVec = PackedVec;
- static constexpr int CVT_FP4_NUM_THREADS_PER_SF = (CVT_FP4_SF_VEC_SIZE / CVT_FP4_ELTS_PER_THREAD);
- static_assert(sizeof(PackedVec) == sizeof(Type) * CVT_FP4_ELTS_PER_THREAD, "Vec size is not matched.");
- extern __shared__ uint32_t shared_input_offsets[];
-
- // Load input offsets into shared memory.
- // If n_experts is larger than 4, use vectorized int4 to save instructions.
- // If n_experts is smaller than 4, read directly.
- if constexpr (SMALL_NUM_EXPERTS) {
- for (int i = threadIdx.x; i < n_experts + 1; i += blockDim.x) {
- shared_input_offsets[i] = input_offset_by_experts[i];
- }
- } else {
- for (int i = threadIdx.x * 4; i < n_experts; i += blockDim.x * 4) {
- *reinterpret_cast(&shared_input_offsets[i]) = *reinterpret_cast(&input_offset_by_experts[i]);
- }
- if (threadIdx.x == 0) {
- shared_input_offsets[n_experts] = input_offset_by_experts[n_experts];
- }
- }
-
- __syncthreads();
-
- int tid = blockIdx.x * blockDim.x + threadIdx.x;
- int colsPerRow = numCols / CVT_FP4_ELTS_PER_THREAD;
- bool use_mask = mask != nullptr;
- // When use_silu_and_mul is true, input last dim is 2*k (gate+up concatenated).
- int actualColsPerRow = (use_mask || use_silu_and_mul) ? colsPerRow * 2 : colsPerRow;
-
- // Each global thread processes one element
- for (int globalIdx = tid; globalIdx < numRows * colsPerRow; globalIdx += gridDim.x * blockDim.x) {
- // Calculate which row and column this global thread should process
- int rowIdx = globalIdx / colsPerRow;
- int colIdx = globalIdx % colsPerRow;
-
- // Find expert using binary search for better performance with large m_topk
- int rowIdx_in_expert = 0;
- int expert_idx = 0;
-
- // Binary search through experts using shared memory
- int left = 0, right = n_experts - 1;
- while (left <= right) {
- int mid = (left + right) / 2;
- // Get offsets: shared_input_offsets[i] corresponds to
- // input_offset_by_experts[i]
- uint32_t mid_offset = shared_input_offsets[mid];
- uint32_t next_offset = shared_input_offsets[mid + 1];
-
- if (rowIdx >= mid_offset && rowIdx < next_offset) {
- rowIdx_in_expert = rowIdx - mid_offset;
- expert_idx = mid;
- break;
- } else if (rowIdx < mid_offset) {
- right = mid - 1;
- } else {
- left = mid + 1;
- }
- }
-
- if (use_mask && rowIdx_in_expert >= mask[expert_idx]) {
- continue;
- }
-
- int64_t inOffset = rowIdx * actualColsPerRow + colIdx;
-
- PackedVec in_vec = reinterpret_cast(in)[inOffset];
- if (use_mask || use_silu_and_mul) {
- PackedVec in_vec_mul = reinterpret_cast(in)[inOffset + colsPerRow];
- silu_and_mul(in_vec, in_vec_mul);
- }
-
- int64_t outOffset = rowIdx * colsPerRow + colIdx;
- auto& out_pos = out[outOffset];
-
- float const SFScaleVal = SFScale == nullptr ? 1.0f : SFScale[expert_idx];
-
- int factor = CVT_FP4_SF_VEC_SIZE * 4;
- int32_t numCols_padded = (numCols + factor - 1) / factor * factor;
- int numCols_SFout = numCols_padded / CVT_FP4_SF_VEC_SIZE / 4;
- uint32_t* SFout_in_expert = SFout + output_scale_offset_by_experts[expert_idx] * numCols_SFout;
-
- auto sf_out = cvt_quant_to_fp4_get_sf_out_offset(
- rowIdx_in_expert, colIdx, numCols, SFout_in_expert);
-
- out_pos = cvt_warp_fp16_to_fp4(in_vec, SFScaleVal, sf_out);
- }
-#endif
-}
-
-template
-void quant_impl(
- void* output,
- void* output_scale,
- void* input,
- void* input_global_scale,
- void* input_offset_by_experts,
- void* output_scale_offset_by_experts,
- void* mask,
- bool use_silu_and_mul,
- int m_topk,
- int k,
- int n_experts,
- cudaStream_t stream) {
- // TODO: this multiProcessorCount should be cached.
- int device;
- cudaGetDevice(&device);
- int multiProcessorCount;
- cudaDeviceGetAttribute(&multiProcessorCount, cudaDevAttrMultiProcessorCount, device);
-
- // Grid, Block size.
- // Each thread converts 8 values.
- int const workSizePerRow = k / ELTS_PER_THREAD;
- int const totalWorkSize = m_topk * workSizePerRow;
- dim3 block(std::min(workSizePerRow, 512));
- // Get number of blocks per SM (assume we can fully utilize the SM).
- int const numBlocksPerSM = 2048 / block.x;
- dim3 grid(std::min(static_cast((totalWorkSize + block.x - 1) / block.x), multiProcessorCount * numBlocksPerSM));
- while (grid.x <= multiProcessorCount && block.x > 64) {
- grid.x *= 2;
- block.x = (block.x + 1) / 2;
- }
-
- // TODO(kaixih@nvidia): Should relax this to allow any grid size.
- if (mask != nullptr) {
- grid.x = (grid.x + n_experts - 1) / n_experts * n_experts;
- cvt_fp16_to_fp4_expert<<>>(
- m_topk,
- k,
- reinterpret_cast(input),
- reinterpret_cast(input_global_scale),
- reinterpret_cast(output),
- reinterpret_cast(output_scale),
- reinterpret_cast(mask),
- use_silu_and_mul,
- n_experts);
- return;
- }
-
- int const blockRepeat = (totalWorkSize + block.x * grid.x - 1) / (block.x * grid.x);
- if (blockRepeat > 1) {
- size_t shared_mem_size = (n_experts + 1) * sizeof(uint32_t);
- if (n_experts >= 4) {
- cvt_fp16_to_fp4<<>>(
- m_topk,
- k,
- reinterpret_cast(input),
- reinterpret_cast(input_global_scale),
- reinterpret_cast(output),
- reinterpret_cast(output_scale),
- reinterpret_cast(input_offset_by_experts),
- reinterpret_cast(output_scale_offset_by_experts),
- reinterpret_cast(mask),
- n_experts,
- use_silu_and_mul);
- } else {
- cvt_fp16_to_fp4<<>>(
- m_topk,
- k,
- reinterpret_cast(input),
- reinterpret_cast(input_global_scale),
- reinterpret_cast(output),
- reinterpret_cast(output_scale),
- reinterpret_cast(input_offset_by_experts),
- reinterpret_cast(output_scale_offset_by_experts),
- reinterpret_cast(mask),
- n_experts,
- use_silu_and_mul);
- }
- } else {
- if (n_experts >= 16) {
- cvt_fp16_to_fp4<<>>(
- m_topk,
- k,
- reinterpret_cast(input),
- reinterpret_cast(input_global_scale),
- reinterpret_cast(output),
- reinterpret_cast(output_scale),
- reinterpret_cast(input_offset_by_experts),
- reinterpret_cast(output_scale_offset_by_experts),
- reinterpret_cast(mask),
- n_experts,
- /* bool low_latency */ true,
- use_silu_and_mul);
- } else {
- cvt_fp16_to_fp4<<>>(
- m_topk,
- k,
- reinterpret_cast(input),
- reinterpret_cast(input_global_scale),
- reinterpret_cast(output),
- reinterpret_cast(output_scale),
- reinterpret_cast(input_offset_by_experts),
- reinterpret_cast(output_scale_offset_by_experts),
- reinterpret_cast(mask),
- n_experts,
- /* bool low_latency */ true,
- use_silu_and_mul);
- }
- }
-}
-
-inline int getSMVersion(int device_id) {
- int sm_major = 0;
- int sm_minor = 0;
- RuntimeDeviceCheck(cudaDeviceGetAttribute(&sm_major, cudaDevAttrComputeCapabilityMajor, device_id));
- RuntimeDeviceCheck(cudaDeviceGetAttribute(&sm_minor, cudaDevAttrComputeCapabilityMinor, device_id));
- return sm_major * 10 + sm_minor;
-}
-
-void scaled_fp4_experts_quant_sm100a(
- tvm::ffi::TensorView output,
- tvm::ffi::TensorView output_scale,
- tvm::ffi::TensorView input,
- tvm::ffi::TensorView input_global_scale,
- tvm::ffi::TensorView input_offset_by_experts,
- tvm::ffi::TensorView output_scale_offset_by_experts) {
- auto MTopK = SymbolicSize{"m_topk"};
- auto K = SymbolicSize{"k"};
- auto OutputCols = SymbolicSize{"output_cols"};
- auto OutputScaleRows = SymbolicSize{"output_scale_rows"};
- auto OutputScaleCols = SymbolicSize{"output_scale_cols"};
- auto NExperts = SymbolicSize{"n_experts"};
- auto OffsetSize = SymbolicSize{"offset_size"};
- auto device = SymbolicDevice{};
-
- TensorMatcher({MTopK, K}) //
- .with_dtype()
- .template with_device(device)
- .verify(input);
- TensorMatcher({MTopK, OutputCols}) //
- .with_dtype()
- .with_device(device)
- .verify(output);
- TensorMatcher({OutputScaleRows, OutputScaleCols}) //
- .with_dtype()
- .with_device(device)
- .verify(output_scale);
- TensorMatcher({NExperts}) //
- .with_dtype()
- .with_device(device)
- .verify(input_global_scale);
- TensorMatcher({OffsetSize}) //
- .with_dtype()
- .with_device(device)
- .verify(input_offset_by_experts)
- .verify(output_scale_offset_by_experts);
-
- const int device_id = input.device().device_id;
- RuntimeCheck(getSMVersion(device_id) >= 100, "fp4_quant is only supported on sm100+");
-
- const int BLOCK_SIZE = 16;
- const auto m_topk = static_cast(MTopK.unwrap());
- const auto k = static_cast(K.unwrap());
- RuntimeCheck(k % BLOCK_SIZE == 0, "k must be a multiple of 16");
- const auto n_experts = static_cast(NExperts.unwrap());
- const auto offset_size = static_cast(OffsetSize.unwrap());
- RuntimeCheck(offset_size == n_experts + 1, "input/output offset size mismatch");
- RuntimeCheck(static_cast(OutputCols.unwrap()) == k / 2, "output second dim mismatch");
- const int scales_k = k / BLOCK_SIZE;
- const int padded_k = (scales_k + 3) / 4 * 4;
- RuntimeCheck(static_cast(OutputScaleCols.unwrap()) * 4 == padded_k, "output_scale second dim mismatch");
-
- const cudaStream_t stream = LaunchKernel::resolve_device(input.device());
- if (host::is_type(input.dtype())) {
- quant_impl(
- output.data_ptr(),
- output_scale.data_ptr(),
- input.data_ptr(),
- input_global_scale.data_ptr(),
- input_offset_by_experts.data_ptr(),
- output_scale_offset_by_experts.data_ptr(),
- nullptr, // mask
- false, // use_silu_and_mul
- m_topk,
- k,
- n_experts,
- stream);
- } else {
- quant_impl<__nv_bfloat16>(
- output.data_ptr(),
- output_scale.data_ptr(),
- input.data_ptr(),
- input_global_scale.data_ptr(),
- input_offset_by_experts.data_ptr(),
- output_scale_offset_by_experts.data_ptr(),
- nullptr, // mask
- false, // use_silu_and_mul
- m_topk,
- k,
- n_experts,
- stream);
- }
-}
-
-void silu_and_mul_scaled_fp4_experts_quant_sm100a(
- tvm::ffi::TensorView output,
- tvm::ffi::TensorView output_scale,
- tvm::ffi::TensorView input,
- tvm::ffi::TensorView input_global_scale,
- tvm::ffi::TensorView mask,
- bool use_silu_and_mul) {
- auto MTopK = SymbolicSize{"m_topk"};
- auto KBy2 = SymbolicSize{"k_by_2"};
- auto OutputCols = SymbolicSize{"output_cols"};
- auto OutputScaleRows = SymbolicSize{"output_scale_rows"};
- auto OutputScaleCols = SymbolicSize{"output_scale_cols"};
- auto NExperts = SymbolicSize{"n_experts"};
- auto device = SymbolicDevice{};
-
- TensorMatcher({MTopK, KBy2}) //
- .with_dtype()
- .template with_device(device)
- .verify(input);
- TensorMatcher({MTopK, OutputCols}) //
- .with_dtype()
- .with_device(device)
- .verify(output);
- TensorMatcher({OutputScaleRows, OutputScaleCols}) //
- .with_dtype()
- .with_device(device)
- .verify(output_scale);
- TensorMatcher({NExperts}) //
- .with_dtype()
- .with_device(device)
- .verify(input_global_scale);
- TensorMatcher({NExperts}) //
- .with_dtype()
- .with_device(device)
- .verify(mask);
-
- const int device_id = input.device().device_id;
- RuntimeCheck(getSMVersion(device_id) >= 100, "fp4_quant is only supported on sm100+");
-
- const int BLOCK_SIZE = 16;
- const auto m_topk = static_cast(MTopK.unwrap());
- const auto k_by_2 = static_cast(KBy2.unwrap());
- int k = k_by_2;
- if (use_silu_and_mul) {
- RuntimeCheck(k_by_2 % 2 == 0, "k must be a multiple of 2");
- k = k_by_2 / 2;
- }
- const auto n_experts = static_cast(NExperts.unwrap());
- RuntimeCheck(static_cast(OutputCols.unwrap()) == k / 2, "output second dim mismatch");
- const int scales_k = k / BLOCK_SIZE;
- const int padded_k = (scales_k + 3) / 4 * 4;
- RuntimeCheck(static_cast(OutputScaleCols.unwrap()) * 4 == padded_k, "output_scale second dim mismatch");
-
- const cudaStream_t stream = LaunchKernel::resolve_device(input.device());
- if (host::is_type(input.dtype())) {
- quant_impl(
- output.data_ptr(),
- output_scale.data_ptr(),
- input.data_ptr(),
- input_global_scale.data_ptr(),
- nullptr, // input_offset_by_experts
- nullptr, // output_scale_offset_by_experts
- mask.data_ptr(),
- use_silu_and_mul,
- m_topk,
- k,
- n_experts,
- stream);
- } else {
- quant_impl<__nv_bfloat16>(
- output.data_ptr(),
- output_scale.data_ptr(),
- input.data_ptr(),
- input_global_scale.data_ptr(),
- nullptr, // input_offset_by_experts
- nullptr, // output_scale_offset_by_experts
- mask.data_ptr(),
- use_silu_and_mul,
- m_topk,
- k,
- n_experts,
- stream);
- }
-}
-
-void silu_and_mul_scaled_fp4_experts_quant_packed_sm100a(
- tvm::ffi::TensorView output,
- tvm::ffi::TensorView output_scale,
- tvm::ffi::TensorView input,
- tvm::ffi::TensorView input_global_scale,
- tvm::ffi::TensorView input_offset_by_experts,
- tvm::ffi::TensorView output_scale_offset_by_experts) {
- auto MTopK = SymbolicSize{"m_topk"};
- auto KBy2 = SymbolicSize{"k_by_2"};
- auto OutputCols = SymbolicSize{"output_cols"};
- auto OutputScaleRows = SymbolicSize{"output_scale_rows"};
- auto OutputScaleCols = SymbolicSize{"output_scale_cols"};
- auto NExperts = SymbolicSize{"n_experts"};
- auto OffsetSize = SymbolicSize{"offset_size"};
- auto device = SymbolicDevice{};
-
- TensorMatcher({MTopK, KBy2}) //
- .with_dtype()
- .template with_device(device)
- .verify(input);
- TensorMatcher({MTopK, OutputCols}) //
- .with_dtype()
- .with_device(device)
- .verify(output);
- TensorMatcher({OutputScaleRows, OutputScaleCols}) //
- .with_dtype()
- .with_device(device)
- .verify(output_scale);
- TensorMatcher({NExperts}) //
- .with_dtype()
- .with_device(device)
- .verify(input_global_scale);
- TensorMatcher({OffsetSize}) //
- .with_dtype()
- .with_device(device)
- .verify(input_offset_by_experts)
- .verify(output_scale_offset_by_experts);
-
- const int device_id = input.device().device_id;
- RuntimeCheck(getSMVersion(device_id) >= 100, "fp4_quant is only supported on sm100+");
-
- const int BLOCK_SIZE = 16;
- const auto m_topk = static_cast(MTopK.unwrap());
- const auto k_by_2 = static_cast(KBy2.unwrap());
- // Input last dim is 2*k (gate+up concatenated). The kernel does SiLU(gate)*up
- // then FP4-quantizes the k-dim result.
- RuntimeCheck(k_by_2 % 2 == 0, "input last dim must be even (2*k)");
- const int k = k_by_2 / 2;
- RuntimeCheck(k % BLOCK_SIZE == 0, "k must be a multiple of 16");
- const auto n_experts = static_cast(NExperts.unwrap());
- const auto offset_size = static_cast(OffsetSize.unwrap());
- RuntimeCheck(offset_size == n_experts + 1, "input/output offset size mismatch");
- RuntimeCheck(static_cast(OutputCols.unwrap()) == k / 2, "output second dim mismatch");
- const int scales_k = k / BLOCK_SIZE;
- const int padded_k = (scales_k + 3) / 4 * 4;
- RuntimeCheck(static_cast(OutputScaleCols.unwrap()) * 4 == padded_k, "output_scale second dim mismatch");
-
- const cudaStream_t stream = LaunchKernel::resolve_device(input.device());
- if (host::is_type(input.dtype())) {
- quant_impl(
- output.data_ptr(),
- output_scale.data_ptr(),
- input.data_ptr(),
- input_global_scale.data_ptr(),
- input_offset_by_experts.data_ptr(),
- output_scale_offset_by_experts.data_ptr(),
- nullptr, // mask
- true, // use_silu_and_mul
- m_topk,
- k,
- n_experts,
- stream);
- } else {
- quant_impl<__nv_bfloat16>(
- output.data_ptr(),
- output_scale.data_ptr(),
- input.data_ptr(),
- input_global_scale.data_ptr(),
- input_offset_by_experts.data_ptr(),
- output_scale_offset_by_experts.data_ptr(),
- nullptr, // mask
- true, // use_silu_and_mul
- m_topk,
- k,
- n_experts,
- stream);
- }
-}
diff --git a/python/sglang/jit_kernel/csrc/gemm/nvfp4/nvfp4_quant.cuh b/python/sglang/jit_kernel/csrc/gemm/nvfp4/nvfp4_quant.cuh
deleted file mode 100644
index e2d696673..000000000
--- a/python/sglang/jit_kernel/csrc/gemm/nvfp4/nvfp4_quant.cuh
+++ /dev/null
@@ -1,160 +0,0 @@
-/* Copyright 2025 SGLang Team. All Rights Reserved.
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-==============================================================================*/
-
-#include
-#include
-
-#include
-
-#include
-#include
-
-#define ELTS_PER_THREAD 8
-
-constexpr int CVT_FP4_ELTS_PER_THREAD = 8;
-constexpr int CVT_FP4_SF_VEC_SIZE = 16;
-
-// Convert 8 float32 values into 8 e2m1 values (represented as one uint32_t).
-SGL_DEVICE uint32_t fp32_vec_to_e2m1(float (&array)[8]) {
- // PTX instructions used here requires >= sm100f.
-#if CUTLASS_ARCH_MMA_SM100A_ENABLED || CUTLASS_ARCH_MMA_SM103A_ENABLED || CUTLASS_ARCH_MMA_SM120A_ENABLED || \
- (defined(__CUDA_ARCH_FAMILY_SPECIFIC__) && (__CUDA_ARCH_FAMILY_SPECIFIC__ >= 1000))
- uint32_t val;
- asm volatile(
- "{\n"
- ".reg .b8 byte0;\n"
- ".reg .b8 byte1;\n"
- ".reg .b8 byte2;\n"
- ".reg .b8 byte3;\n"
- "cvt.rn.satfinite.e2m1x2.f32 byte0, %2, %1;\n"
- "cvt.rn.satfinite.e2m1x2.f32 byte1, %4, %3;\n"
- "cvt.rn.satfinite.e2m1x2.f32 byte2, %6, %5;\n"
- "cvt.rn.satfinite.e2m1x2.f32 byte3, %8, %7;\n"
- "mov.b32 %0, {byte0, byte1, byte2, byte3};\n"
- "}"
- : "=r"(val)
- : "f"(array[0]),
- "f"(array[1]),
- "f"(array[2]),
- "f"(array[3]),
- "f"(array[4]),
- "f"(array[5]),
- "f"(array[6]),
- "f"(array[7]));
- return val;
-#else
- printf("fp32_vec_to_e2m1 is not supported on this architecture\n");
- __trap();
- return 0;
-#endif
-}
-
-// Convert 4 float2 values into 8 e2m1 values (represented as one uint32_t).
-SGL_DEVICE uint32_t fp32_vec_to_e2m1(float2 (&array)[4]) {
- // PTX instructions used here requires >= sm100f.
-#if CUTLASS_ARCH_MMA_SM100A_ENABLED || CUTLASS_ARCH_MMA_SM103A_ENABLED || CUTLASS_ARCH_MMA_SM120A_ENABLED || \
- (defined(__CUDA_ARCH_FAMILY_SPECIFIC__) && (__CUDA_ARCH_FAMILY_SPECIFIC__ >= 1000))
- uint32_t val;
- asm volatile(
- "{\n"
- ".reg .b8 byte0;\n"
- ".reg .b8 byte1;\n"
- ".reg .b8 byte2;\n"
- ".reg .b8 byte3;\n"
- "cvt.rn.satfinite.e2m1x2.f32 byte0, %2, %1;\n"
- "cvt.rn.satfinite.e2m1x2.f32 byte1, %4, %3;\n"
- "cvt.rn.satfinite.e2m1x2.f32 byte2, %6, %5;\n"
- "cvt.rn.satfinite.e2m1x2.f32 byte3, %8, %7;\n"
- "mov.b32 %0, {byte0, byte1, byte2, byte3};\n"
- "}"
- : "=r"(val)
- : "f"(array[0].x),
- "f"(array[0].y),
- "f"(array[1].x),
- "f"(array[1].y),
- "f"(array[2].x),
- "f"(array[2].y),
- "f"(array[3].x),
- "f"(array[3].y));
- return val;
-#else
- printf("fp32_vec_to_e2m1 is not supported on this architecture\n");
- __trap();
- return 0;
-#endif
-}
-
-// Fast reciprocal.
-SGL_DEVICE float reciprocal_approximate_ftz(float a) {
- float b;
- asm volatile("rcp.approx.ftz.f32 %0, %1;\n" : "=f"(b) : "f"(a));
- return b;
-}
-
-template
-SGL_DEVICE uint8_t* cvt_quant_to_fp4_get_sf_out_offset(int rowIdx, int colIdx, int numCols, SFType* SFout) {
-#if defined(__CUDA_ARCH__) && (__CUDA_ARCH__ >= 1000)
- static_assert(CVT_FP4_NUM_THREADS_PER_SF == 1 || CVT_FP4_NUM_THREADS_PER_SF == 2);
-
- // One pair of threads write one SF to global memory.
- // TODO: stage through smem for packed STG.32
- // is it better than STG.8 from 4 threads ?
- if (threadIdx.x % CVT_FP4_NUM_THREADS_PER_SF == 0) {
- // SF vector index (16 elements share one SF in the K dimension).
- int32_t kIdx = colIdx / CVT_FP4_NUM_THREADS_PER_SF;
- int32_t mIdx = rowIdx;
-
- // SF layout [numMTiles, numKTiles, 32 (mTile), 4 (mTile), 4(kTile)]
- // --> index [mTileIdx, kTileIdx, outerMIdx, innerMIdx, innerKIdx]
-
- int32_t mTileIdx = mIdx / (32 * 4);
- // SF vector size 16.
- int factor = CVT_FP4_SF_VEC_SIZE * 4;
- int32_t numKTiles = (numCols + factor - 1) / factor;
- int64_t mTileStride = numKTiles * 32 * 4 * 4;
-
- int32_t kTileIdx = (kIdx / 4);
- int64_t kTileStride = 32 * 4 * 4;
-
- // M tile layout [32, 4] is column-major.
- int32_t outerMIdx = (mIdx % 32);
- int64_t outerMStride = 4 * 4;
-
- int32_t innerMIdx = (mIdx % (32 * 4)) / 32;
- int64_t innerMStride = 4;
-
- int32_t innerKIdx = (kIdx % 4);
- int64_t innerKStride = 1;
-
- // Compute the global offset.
- int64_t SFOffset = mTileIdx * mTileStride + kTileIdx * kTileStride + outerMIdx * outerMStride +
- innerMIdx * innerMStride + innerKIdx * innerKStride;
-
- return reinterpret_cast(SFout) + SFOffset;
- }
-#endif
- return nullptr;
-}
-
-// Define a 16 bytes packed data type.
-template
-struct PackedVec {
- packed_t elts[4];
-};
-
-template <>
-struct PackedVec<__nv_fp8_e4m3> {
- __nv_fp8x2_e4m3 elts[8];
-};
diff --git a/python/sglang/jit_kernel/csrc/gemm/nvfp4/nvfp4_quant_entry.cuh b/python/sglang/jit_kernel/csrc/gemm/nvfp4/nvfp4_quant_entry.cuh
deleted file mode 100644
index f2bd76787..000000000
--- a/python/sglang/jit_kernel/csrc/gemm/nvfp4/nvfp4_quant_entry.cuh
+++ /dev/null
@@ -1,87 +0,0 @@
-/* Copyright 2025 SGLang Team. All Rights Reserved.
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-==============================================================================*/
-
-#include
-#include
-
-void scaled_fp4_quant_sm100a_sm120a(
- tvm::ffi::TensorView output,
- tvm::ffi::TensorView input,
- tvm::ffi::TensorView output_sf,
- tvm::ffi::TensorView input_sf);
-
-void scaled_fp4_experts_quant_sm100a(
- tvm::ffi::TensorView output,
- tvm::ffi::TensorView output_scale,
- tvm::ffi::TensorView input,
- tvm::ffi::TensorView input_global_scale,
- tvm::ffi::TensorView input_offset_by_experts,
- tvm::ffi::TensorView output_scale_offset_by_experts);
-
-void silu_and_mul_scaled_fp4_experts_quant_sm100a(
- tvm::ffi::TensorView output,
- tvm::ffi::TensorView output_scale,
- tvm::ffi::TensorView input,
- tvm::ffi::TensorView input_global_scale,
- tvm::ffi::TensorView mask,
- bool use_silu_and_mul);
-
-void silu_and_mul_scaled_fp4_experts_quant_packed_sm100a(
- tvm::ffi::TensorView output,
- tvm::ffi::TensorView output_scale,
- tvm::ffi::TensorView input,
- tvm::ffi::TensorView input_global_scale,
- tvm::ffi::TensorView input_offset_by_experts,
- tvm::ffi::TensorView output_scale_offset_by_experts);
-
-void scaled_fp4_quant(
- tvm::ffi::TensorView output,
- tvm::ffi::TensorView input,
- tvm::ffi::TensorView output_sf,
- tvm::ffi::TensorView input_sf) {
- scaled_fp4_quant_sm100a_sm120a(output, input, output_sf, input_sf);
-}
-
-void scaled_fp4_experts_quant(
- tvm::ffi::TensorView output,
- tvm::ffi::TensorView output_scale,
- tvm::ffi::TensorView input,
- tvm::ffi::TensorView input_global_scale,
- tvm::ffi::TensorView input_offset_by_experts,
- tvm::ffi::TensorView output_scale_offset_by_experts) {
- scaled_fp4_experts_quant_sm100a(
- output, output_scale, input, input_global_scale, input_offset_by_experts, output_scale_offset_by_experts);
-}
-
-void silu_and_mul_scaled_fp4_experts_quant(
- tvm::ffi::TensorView output,
- tvm::ffi::TensorView output_scale,
- tvm::ffi::TensorView input,
- tvm::ffi::TensorView input_global_scale,
- tvm::ffi::TensorView mask,
- bool use_silu_and_mul) {
- silu_and_mul_scaled_fp4_experts_quant_sm100a(output, output_scale, input, input_global_scale, mask, use_silu_and_mul);
-}
-
-void silu_and_mul_scaled_fp4_experts_quant_packed(
- tvm::ffi::TensorView output,
- tvm::ffi::TensorView output_scale,
- tvm::ffi::TensorView input,
- tvm::ffi::TensorView input_global_scale,
- tvm::ffi::TensorView input_offset_by_experts,
- tvm::ffi::TensorView output_scale_offset_by_experts) {
- silu_and_mul_scaled_fp4_experts_quant_packed_sm100a(
- output, output_scale, input, input_global_scale, input_offset_by_experts, output_scale_offset_by_experts);
-}
diff --git a/python/sglang/jit_kernel/csrc/gemm/nvfp4/nvfp4_quant_kernels.cuh b/python/sglang/jit_kernel/csrc/gemm/nvfp4/nvfp4_quant_kernels.cuh
deleted file mode 100644
index bac38d83e..000000000
--- a/python/sglang/jit_kernel/csrc/gemm/nvfp4/nvfp4_quant_kernels.cuh
+++ /dev/null
@@ -1,241 +0,0 @@
-/* Copyright 2025 SGLang Team. All Rights Reserved.
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-==============================================================================*/
-
-#include
-#include
-
-#include
-#include
-
-#include "nvfp4_quant.cuh"
-#include
-#include
-
-using namespace host;
-
-// Quantizes the provided PackedVec into the uint32_t output
-template
-SGL_DEVICE uint32_t cvt_warp_fp16_to_fp4(PackedVec& vec, float SFScaleVal, uint8_t* SFout) {
-#if defined(__CUDA_ARCH__) && (__CUDA_ARCH__ >= 1000)
- // Get absolute maximum values among the local 8 values.
- auto localMax = __habs2(vec.elts[0]);
-
-// Local maximum value.
-#pragma unroll
- for (int i = 1; i < CVT_FP4_ELTS_PER_THREAD / 2; i++) {
- localMax = __hmax2(localMax, __habs2(vec.elts[i]));
- }
-
- // Get the absolute maximum among all 16 values (two threads).
- localMax = __hmax2(__shfl_xor_sync(uint32_t(-1), localMax, 1), localMax);
- // Get the final absolute maximum values.
- float vecMax = float(__hmax(localMax.x, localMax.y));
-
- // Get the SF (max value of the vector / max value of e2m1).
- // maximum value of e2m1 = 6.0.
- // TODO: use half as compute data type.
- float SFValue = SFScaleVal * (vecMax * reciprocal_approximate_ftz(6.0f));
- // 8 bits representation of the SF.
- uint8_t fp8SFVal;
- // Write the SF to global memory (STG.8).
- if constexpr (UE8M0_SF) {
- __nv_fp8_e8m0 tmp;
- tmp.__x = __nv_cvt_float_to_e8m0(SFValue, __NV_SATFINITE, cudaRoundPosInf);
- SFValue = static_cast(tmp);
- fp8SFVal = tmp.__x;
- } else {
- // Here SFValue is always positive, so E4M3 is the same as UE4M3.
- __nv_fp8_e4m3 tmp = __nv_fp8_e4m3(SFValue);
- fp8SFVal = tmp.__x;
- SFValue = static_cast(tmp);
- }
- // Get the output scale.
- // Recipe: final_scale = reciprocal(fp32(fp8(SFValue * SFScaleVal))) *
- // reciprocal(SFScaleVal))
- float outputScale =
- SFValue != 0 ? reciprocal_approximate_ftz(SFValue * reciprocal_approximate_ftz(SFScaleVal)) : 0.0f;
-
- if (SFout) {
- // Write the SF to global memory (STG.8).
- *SFout = fp8SFVal;
- }
-
- // Convert the input to float.
- float2 fp2Vals[CVT_FP4_ELTS_PER_THREAD / 2];
-
-#pragma unroll
- for (int i = 0; i < CVT_FP4_ELTS_PER_THREAD / 2; i++) {
- if constexpr (std::is_same_v) {
- fp2Vals[i] = __half22float2(vec.elts[i]);
- } else {
- fp2Vals[i] = __bfloat1622float2(vec.elts[i]);
- }
- fp2Vals[i].x *= outputScale;
- fp2Vals[i].y *= outputScale;
- }
-
- // Convert to e2m1 values.
- uint32_t e2m1Vec = fp32_vec_to_e2m1(fp2Vals);
-
- // Write the e2m1 values to global memory.
- return e2m1Vec;
-#else
- return 0;
-#endif
-}
-
-// Use UE4M3 by default.
-template
-__global__ void
-#if defined(__CUDA_ARCH__) && (__CUDA_ARCH__ >= 1000)
-__launch_bounds__(512, 4) cvt_fp16_to_fp4(
-#else
-cvt_fp16_to_fp4(
-#endif
- int32_t numRows, int32_t numCols, Type const* in, float const* SFScale, uint32_t* out, uint32_t* SFout) {
-#if defined(__CUDA_ARCH__) && (__CUDA_ARCH__ >= 1000)
- using PackedVec = PackedVec;
- static constexpr int CVT_FP4_NUM_THREADS_PER_SF = (CVT_FP4_SF_VEC_SIZE / CVT_FP4_ELTS_PER_THREAD);
- static_assert(sizeof(PackedVec) == sizeof(Type) * CVT_FP4_ELTS_PER_THREAD, "Vec size is not matched.");
-
- // Get the global scaling factor, which will be applied to the SF.
- // Note SFScale is the same as next GEMM's alpha, which is
- // (448.f / (Alpha_A / 6.f)).
- float const SFScaleVal = SFScale == nullptr ? 1.0f : SFScale[0];
-
- // Input tensor row/col loops.
- for (int rowIdx = blockIdx.x; rowIdx < numRows; rowIdx += gridDim.x) {
- for (int colIdx = threadIdx.x; colIdx < numCols / CVT_FP4_ELTS_PER_THREAD; colIdx += blockDim.x) {
- int64_t inOffset = rowIdx * (numCols / CVT_FP4_ELTS_PER_THREAD) + colIdx;
- PackedVec in_vec = reinterpret_cast(in)[inOffset];
- // Get the output tensor offset.
- // Same as inOffset because 8 elements are packed into one uint32_t.
- int64_t outOffset = inOffset;
- auto& out_pos = out[outOffset];
-
- auto sf_out =
- cvt_quant_to_fp4_get_sf_out_offset(rowIdx, colIdx, numCols, SFout);
-
- out_pos = cvt_warp_fp16_to_fp4(in_vec, SFScaleVal, sf_out);
- }
- }
-#endif
-}
-
-template
-void invokeFP4Quantization(
- int m,
- int n,
- T const* input,
- float const* SFScale,
- int64_t* output,
- int32_t* SFOuput,
- bool useUE8M0,
- int multiProcessorCount,
- cudaStream_t stream) {
- // Grid, Block size.
- // Each thread converts 8 values.
- dim3 block(std::min(int(n / ELTS_PER_THREAD), 512));
- // Get number of blocks per SM (assume we can fully utilize the SM).
- int const numBlocksPerSM = 2048 / block.x;
- dim3 grid(std::min(int(m), multiProcessorCount * numBlocksPerSM));
-
- // Launch the cvt kernel.
- if (useUE8M0) {
- cvt_fp16_to_fp4<<>>(
- m, n, input, SFScale, reinterpret_cast(output), reinterpret_cast(SFOuput));
- } else {
- cvt_fp16_to_fp4<<>>(
- m, n, input, SFScale, reinterpret_cast(output), reinterpret_cast(SFOuput));
- }
-}
-
-// Instantiate the function.
-template void invokeFP4Quantization(
- int m,
- int n,
- half const* input,
- float const* SFScale,
- int64_t* output,
- int32_t* SFOuput,
- bool useUE8M0,
- int multiProcessorCount,
- cudaStream_t stream);
-
-template void invokeFP4Quantization(
- int m,
- int n,
- __nv_bfloat16 const* input,
- float const* SFScale,
- int64_t* output,
- int32_t* SFOuput,
- bool useUE8M0,
- int multiProcessorCount,
- cudaStream_t stream);
-
-inline int getSMVersion(int device_id) {
- int sm_major = 0;
- int sm_minor = 0;
- RuntimeDeviceCheck(cudaDeviceGetAttribute(&sm_major, cudaDevAttrComputeCapabilityMajor, device_id));
- RuntimeDeviceCheck(cudaDeviceGetAttribute(&sm_minor, cudaDevAttrComputeCapabilityMinor, device_id));
- return sm_major * 10 + sm_minor;
-}
-
-void scaled_fp4_quant_sm100a_sm120a(
- tvm::ffi::TensorView output,
- tvm::ffi::TensorView input,
- tvm::ffi::TensorView output_sf,
- tvm::ffi::TensorView input_sf) {
- RuntimeCheck(input.device().device_type == kDLCUDA, "input must be a CUDA tensor");
- RuntimeCheck(output.device() == input.device(), "output and input must be on same device");
- RuntimeCheck(output_sf.device() == input.device(), "output_sf and input must be on same device");
- RuntimeCheck(input_sf.device() == input.device(), "input_sf and input must be on same device");
- RuntimeCheck(input.dim() == 2, "input must be a 2D tensor");
- RuntimeCheck(output.dim() == 2, "output must be a 2D tensor");
- RuntimeCheck(output_sf.dim() == 2, "output_sf must be a 2D tensor");
- RuntimeCheck(input_sf.numel() == 1, "input_sf must have exactly one element");
- RuntimeCheck(host::is_type(output.dtype()), "output must be uint8");
- RuntimeCheck(host::is_type(output_sf.dtype()), "output_sf must be int32");
- RuntimeCheck(host::is_type(input_sf.dtype()), "input_sf must be float32");
- RuntimeCheck(
- host::is_type(input.dtype()) || host::is_type(input.dtype()), "input dtype must be fp16 or bf16");
-
- const int device_id = input.device().device_id;
- const auto sm_version = getSMVersion(device_id);
- RuntimeCheck(sm_version >= 100, "fp4_quant is only supported on sm100+");
-
- const int32_t m = static_cast(input.size(0));
- const int32_t n = static_cast(input.size(1));
-
- RuntimeCheck(output.size(0) == m, "output row size mismatch");
- RuntimeCheck(output.size(1) == n / 2, "output column size mismatch");
- RuntimeCheck(n % 16 == 0, "The N dimension must be multiple of 16.");
-
- const int multiProcessorCount = static_cast(runtime::get_sm_count(device_id));
-
- auto input_sf_ptr = static_cast(input_sf.data_ptr());
- auto sf_out = static_cast(output_sf.data_ptr());
- auto output_ptr = static_cast(output.data_ptr());
- const cudaStream_t stream = LaunchKernel::resolve_device(input.device());
-
- constexpr bool useUE8M0 = false;
- if (host::is_type(input.dtype())) {
- auto input_ptr = reinterpret_cast(input.data_ptr());
- invokeFP4Quantization(m, n, input_ptr, input_sf_ptr, output_ptr, sf_out, useUE8M0, multiProcessorCount, stream);
- } else {
- auto input_ptr = reinterpret_cast<__nv_bfloat16 const*>(input.data_ptr());
- invokeFP4Quantization(m, n, input_ptr, input_sf_ptr, output_ptr, sf_out, useUE8M0, multiProcessorCount, stream);
- }
-}
diff --git a/python/sglang/jit_kernel/csrc/gemm/nvfp4/nvfp4_scaled_mm_common.cuh b/python/sglang/jit_kernel/csrc/gemm/nvfp4/nvfp4_scaled_mm_common.cuh
deleted file mode 100644
index f5ebca05b..000000000
--- a/python/sglang/jit_kernel/csrc/gemm/nvfp4/nvfp4_scaled_mm_common.cuh
+++ /dev/null
@@ -1,66 +0,0 @@
-/* Copyright 2026 SGLang Team. All Rights Reserved.
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-==============================================================================*/
-
-#pragma once
-
-#include
-#include
-#include
-
-#include
-#include
-
-#include
-#include
-#include
-
-using namespace host;
-
-// clang-format off
-#include "cutlass/cutlass.h"
-#include "cutlass/gemm/collective/collective_builder.hpp"
-#include "cutlass/epilogue/collective/collective_builder.hpp"
-#include "cutlass/gemm/device/gemm_universal_adapter.h"
-#include "cutlass/gemm/kernel/gemm_universal.hpp"
-#include "cutlass/util/packed_stride.hpp"
-// clang-format on
-
-#define CUTLASS_CHECK(status) \
- { \
- cutlass::Status error = status; \
- RuntimeCheck(error == cutlass::Status::kSuccess, cutlassGetStatusString(error)); \
- }
-
-using namespace cute;
-
-inline uint32_t next_pow_2(uint32_t x) noexcept {
- if (x <= 1) return 1;
- return 1u << (32 - __builtin_clz(x - 1));
-}
-
-inline auto alloc_workspace_tensor(size_t required_bytes, DLDevice device) -> tvm::ffi::Tensor {
- if (required_bytes == 0) return {};
- DLDataType u8 = {kDLUInt, 8, 1};
- int64_t shape[] = {static_cast(required_bytes)};
- return ffi::empty(tvm::ffi::ShapeView(shape, 1), u8, device);
-}
-
-inline int getSMVersion(int device_id) {
- int sm_major = 0;
- int sm_minor = 0;
- RuntimeDeviceCheck(cudaDeviceGetAttribute(&sm_major, cudaDevAttrComputeCapabilityMajor, device_id));
- RuntimeDeviceCheck(cudaDeviceGetAttribute(&sm_minor, cudaDevAttrComputeCapabilityMinor, device_id));
- return sm_major * 10 + sm_minor;
-}
diff --git a/python/sglang/jit_kernel/csrc/gemm/nvfp4/nvfp4_scaled_mm_entry.cuh b/python/sglang/jit_kernel/csrc/gemm/nvfp4/nvfp4_scaled_mm_entry.cuh
deleted file mode 100644
index 72d68f7d5..000000000
--- a/python/sglang/jit_kernel/csrc/gemm/nvfp4/nvfp4_scaled_mm_entry.cuh
+++ /dev/null
@@ -1,34 +0,0 @@
-/* Copyright 2025 SGLang Team. All Rights Reserved.
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-==============================================================================*/
-
-#include
-
-void cutlass_scaled_fp4_mm_sm100a_sm120a(
- tvm::ffi::TensorView D,
- tvm::ffi::TensorView A,
- tvm::ffi::TensorView B,
- tvm::ffi::TensorView A_sf,
- tvm::ffi::TensorView B_sf,
- tvm::ffi::TensorView alpha);
-
-void cutlass_scaled_fp4_mm(
- tvm::ffi::TensorView D,
- tvm::ffi::TensorView A,
- tvm::ffi::TensorView B,
- tvm::ffi::TensorView A_sf,
- tvm::ffi::TensorView B_sf,
- tvm::ffi::TensorView alpha) {
- cutlass_scaled_fp4_mm_sm100a_sm120a(D, A, B, A_sf, B_sf, alpha);
-}
diff --git a/python/sglang/jit_kernel/csrc/gemm/nvfp4/nvfp4_scaled_mm_kernels.cuh b/python/sglang/jit_kernel/csrc/gemm/nvfp4/nvfp4_scaled_mm_kernels.cuh
deleted file mode 100644
index 8c5cfefd7..000000000
--- a/python/sglang/jit_kernel/csrc/gemm/nvfp4/nvfp4_scaled_mm_kernels.cuh
+++ /dev/null
@@ -1,146 +0,0 @@
-/* Copyright 2026 SGLang Team. All Rights Reserved.
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-==============================================================================*/
-
-#include "nvfp4_scaled_mm_common.cuh"
-#include "nvfp4_scaled_mm_sm100.cuh"
-#include "nvfp4_scaled_mm_sm120.cuh"
-
-void cutlass_scaled_fp4_mm_sm100a_sm120a(
- tvm::ffi::TensorView D,
- tvm::ffi::TensorView A,
- tvm::ffi::TensorView B,
- tvm::ffi::TensorView A_sf,
- tvm::ffi::TensorView B_sf,
- tvm::ffi::TensorView alpha) {
- RuntimeCheck(A.device().device_type == kDLCUDA, "a must be a CUDA tensor");
- RuntimeCheck(B.device().device_type == kDLCUDA, "b must be a CUDA tensor");
- RuntimeCheck(A_sf.device().device_type == kDLCUDA, "scale_a must be a CUDA tensor");
- RuntimeCheck(B_sf.device().device_type == kDLCUDA, "scale_b must be a CUDA tensor");
- RuntimeCheck(alpha.device().device_type == kDLCUDA, "alpha must be a CUDA tensor");
- RuntimeCheck(D.device().device_type == kDLCUDA, "out must be a CUDA tensor");
-
- RuntimeCheck(A.device() == B.device(), "a and b must be on same device");
- RuntimeCheck(A.device() == A_sf.device(), "a and scale_a must be on same device");
- RuntimeCheck(A.device() == B_sf.device(), "a and scale_b must be on same device");
- RuntimeCheck(A.device() == alpha.device(), "a and alpha must be on same device");
- RuntimeCheck(A.device() == D.device(), "a and out must be on same device");
-
- RuntimeCheck(A.is_contiguous(), "a must be contiguous");
- RuntimeCheck(B.is_contiguous(), "b must be contiguous");
- RuntimeCheck(A_sf.is_contiguous(), "scale_a must be contiguous");
- RuntimeCheck(B_sf.is_contiguous(), "scale_b must be contiguous");
- RuntimeCheck(alpha.is_contiguous(), "alpha must be contiguous");
- RuntimeCheck(D.is_contiguous(), "out must be contiguous");
-
- RuntimeCheck(host::is_type(A.dtype()), "a must be uint8");
- RuntimeCheck(host::is_type(B.dtype()), "b must be uint8");
- RuntimeCheck(host::is_type(A_sf.dtype()), "scale_a must be float8_e4m3fn");
- RuntimeCheck(host::is_type(B_sf.dtype()), "scale_b must be float8_e4m3fn");
- RuntimeCheck(host::is_type(alpha.dtype()), "alpha must be float32");
-
- RuntimeCheck(A.dim() == 2, "a must be a matrix");
- RuntimeCheck(B.dim() == 2, "b must be a matrix");
- RuntimeCheck(A_sf.dim() == 2, "scale_a must be a matrix");
- RuntimeCheck(B_sf.dim() == 2, "scale_b must be a matrix");
- RuntimeCheck(alpha.numel() == 1, "alpha must have exactly one element");
-
- RuntimeCheck(
- A.size(1) == B.size(1),
- "a and b shapes cannot be multiplied (",
- A.size(0),
- "x",
- A.size(1),
- " and ",
- B.size(0),
- "x",
- B.size(1),
- ")");
-
- const auto m = static_cast(A.size(0));
- const auto n = static_cast(B.size(0));
- const auto k = static_cast(A.size(1) * 2);
-
- RuntimeCheck(D.dim() == 2, "out must be 2D");
- RuntimeCheck(D.size(0) == m, "out first dim must equal m");
- RuntimeCheck(D.size(1) == n, "out second dim must equal n");
-
- constexpr int alignment = 32;
- RuntimeCheck(k % alignment == 0, "Expected k to be divisible by ", alignment, ", but got k: ", k);
- RuntimeCheck(n % alignment == 0, "Expected n to be divisible by ", alignment, ", but got n: ", n);
-
- auto round_up = [](int64_t x, int64_t y) { return (x + y - 1) / y * y; };
- const int64_t rounded_m = round_up(m, 128);
- const int64_t rounded_n = round_up(n, 128);
- const int64_t rounded_k = round_up(k / 16, 4);
-
- RuntimeCheck(
- A_sf.size(1) == B_sf.size(1),
- "scale_a and scale_b shapes cannot be multiplied (",
- A_sf.size(0),
- "x",
- A_sf.size(1),
- " and ",
- B_sf.size(0),
- "x",
- B_sf.size(1),
- ")");
- RuntimeCheck(
- A_sf.size(0) == rounded_m && A_sf.size(1) == rounded_k,
- "scale_a must be padded/swizzled to shape (",
- rounded_m,
- "x",
- rounded_k,
- "), got (",
- A_sf.size(0),
- "x",
- A_sf.size(1),
- ")");
- RuntimeCheck(
- B_sf.size(0) == rounded_n && B_sf.size(1) == rounded_k,
- "scale_b must be padded/swizzled to shape (",
- rounded_n,
- "x",
- rounded_k,
- "), got (",
- B_sf.size(0),
- "x",
- B_sf.size(1),
- ")");
-
- const cudaStream_t stream = LaunchKernel::resolve_device(A.device());
- const int sm_version = getSMVersion(A.device().device_id);
-
- if (sm_version >= 120) {
- if (host::is_type(D.dtype())) {
- cutlass_fp4_f16_gemm_dispatch_sm120(
- D, A, B, A_sf, B_sf, alpha, static_cast(m), static_cast(n), static_cast(k), stream);
- } else if (host::is_type(D.dtype())) {
- cutlass_fp4_bf16_gemm_dispatch_sm120(
- D, A, B, A_sf, B_sf, alpha, static_cast(m), static_cast(n), static_cast(k), stream);
- } else {
- Panic("Unsupported output data type of nvfp4 mm sm120");
- }
- } else {
- if (host::is_type(D.dtype())) {
- cutlassFp4GemmDispatchSm100(D, A, B, A_sf, B_sf, alpha, m, n, k, stream);
- } else if (host::is_type(D.dtype())) {
- cutlassFp4GemmDispatchSm100(D, A, B, A_sf, B_sf, alpha, m, n, k, stream);
- } else if (host::is_type(D.dtype())) {
- cutlassFp4GemmDispatchSm100(D, A, B, A_sf, B_sf, alpha, m, n, k, stream);
- } else {
- Panic("Unsupported output data type of nvfp4 mm");
- }
- }
-}
diff --git a/python/sglang/jit_kernel/csrc/gemm/nvfp4/nvfp4_scaled_mm_sm100.cuh b/python/sglang/jit_kernel/csrc/gemm/nvfp4/nvfp4_scaled_mm_sm100.cuh
deleted file mode 100644
index 699bb6236..000000000
--- a/python/sglang/jit_kernel/csrc/gemm/nvfp4/nvfp4_scaled_mm_sm100.cuh
+++ /dev/null
@@ -1,305 +0,0 @@
-/* Copyright 2026 SGLang Team. All Rights Reserved.
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-==============================================================================*/
-
-#pragma once
-
-#include "nvfp4_scaled_mm_common.cuh"
-
-#if defined(CUTLASS_ARCH_MMA_SM100_SUPPORTED)
-
-// Config(half_t/bfloat16_t) for M <= 128
-template
-struct KernelConfigM128 {
- using OutputType = T;
- using MmaTileShape = Shape<_128, _256, _256>;
- using ClusterShape = Shape;
- using EpilogueTile = Shape<_128, _64>; // Avoid register spilling
- using EpilogueSchedule = cutlass::epilogue::TmaWarpSpecialized1Sm;
- using MainloopSchedule = cutlass::gemm::KernelTmaWarpSpecialized1SmNvf4Sm100;
- const static dim3 preferred_cluster;
- const static dim3 fallback_cluster;
-};
-template
-const dim3 KernelConfigM128::preferred_cluster(1, 4, 1);
-template
-const dim3 KernelConfigM128::fallback_cluster(1, 2, 1);
-
-// Config(half_t/bfloat16_t) for M <= 256
-template
-struct KernelConfigM256 {
- using OutputType = T;
- using MmaTileShape = Shape<_256, _256, _256>;
- using ClusterShape = Shape;
- using EpilogueTile = Shape<_128, _64>; // Avoid register spilling
- using EpilogueSchedule = cutlass::epilogue::TmaWarpSpecialized2Sm;
- using MainloopSchedule = cutlass::gemm::KernelTmaWarpSpecialized2SmNvf4Sm100;
- const static dim3 preferred_cluster;
- const static dim3 fallback_cluster;
-};
-template
-const dim3 KernelConfigM256::preferred_cluster(2, 4, 1);
-template
-const dim3 KernelConfigM256::fallback_cluster(2, 1, 1);
-
-// Config(half_t/bfloat16_t) for 256 < M <= 1024
-template
-struct KernelConfigDefault {
- using OutputType = T;
- using MmaTileShape = Shape<_256, _256, _256>;
- using ClusterShape = Shape;
- using EpilogueTile = Shape<_128, _64>; // Avoid register spilling
- using EpilogueSchedule = cutlass::epilogue::TmaWarpSpecialized2Sm;
- using MainloopSchedule = cutlass::gemm::KernelTmaWarpSpecialized2SmNvf4Sm100;
- const static dim3 preferred_cluster;
- const static dim3 fallback_cluster;
-};
-template
-const dim3 KernelConfigDefault::preferred_cluster(2, 4, 1);
-template
-const dim3 KernelConfigDefault::fallback_cluster(2, 1, 1);
-
-// Config(half_t/bfloat16_t) for M > 1024: 1x4 cluster reduces M-tail waste.
-template
-struct KernelConfigLargeM {
- using OutputType = T;
- using MmaTileShape = Shape<_256, _256, _256>;
- using ClusterShape = Shape;
- using EpilogueTile = Shape<_128, _64>;
- using EpilogueSchedule = cutlass::epilogue::TmaWarpSpecialized2Sm;
- using MainloopSchedule = cutlass::gemm::KernelTmaWarpSpecialized2SmNvf4Sm100;
- const static dim3 preferred_cluster;
- const static dim3 fallback_cluster;
-};
-template
-const dim3 KernelConfigLargeM::preferred_cluster(1, 4, 1);
-template
-const dim3 KernelConfigLargeM::fallback_cluster(1, 2, 1);
-
-struct KernelConfigFp32 {
- using OutputType = float;
- using MmaTileShape = Shape<_128, _128, _256>;
- using ClusterShape = Shape;
- using EpilogueTile = cutlass::epilogue::collective::EpilogueTileAuto;
- using EpilogueSchedule = cutlass::epilogue::TmaWarpSpecialized1Sm;
- using MainloopSchedule = cutlass::gemm::KernelTmaWarpSpecialized1SmNvf4Sm100;
- const static dim3 preferred_cluster;
- const static dim3 fallback_cluster;
-};
-const dim3 KernelConfigFp32::preferred_cluster = dim3(1, 4, 1);
-const dim3 KernelConfigFp32::fallback_cluster = dim3(1, 2, 1);
-
-template
-struct Fp4GemmSm100 {
- using Config = KernelConfig;
- using OutputType = typename KernelConfig::OutputType;
-
- using ElementA = cutlass::nv_float4_t;
- using LayoutATag = cutlass::layout::RowMajor;
- static constexpr int AlignmentA = 32;
-
- using ElementB = cutlass::nv_float4_t;
- using LayoutBTag = cutlass::layout::ColumnMajor;
- static constexpr int AlignmentB = 32;
-
- using ElementD = OutputType;
- using ElementC = OutputType;
- using LayoutCTag = cutlass::layout::RowMajor;
- using LayoutDTag = cutlass::layout::RowMajor;
- static constexpr int AlignmentD = 128 / cutlass::sizeof_bits::value;
- static constexpr int AlignmentC = 128 / cutlass::sizeof_bits::value;
-
- using ElementAccumulator = float;
- using ArchTag = cutlass::arch::Sm100;
- using OperatorClass = cutlass::arch::OpClassBlockScaledTensorOp;
-
- using MmaTileShape = typename KernelConfig::MmaTileShape;
- using ClusterShape = typename KernelConfig::ClusterShape;
- using EpilogueTile = typename KernelConfig::EpilogueTile;
- using EpilogueSchedule = typename KernelConfig::EpilogueSchedule;
- using MainloopSchedule = typename KernelConfig::MainloopSchedule;
-
- using CollectiveEpilogue = typename cutlass::epilogue::collective::CollectiveBuilder<
- ArchTag,
- OperatorClass,
- MmaTileShape,
- ClusterShape,
- EpilogueTile,
- ElementAccumulator,
- ElementAccumulator,
- void,
- LayoutCTag,
- AlignmentC,
- ElementD,
- LayoutDTag,
- AlignmentD,
- EpilogueSchedule,
- cutlass::epilogue::fusion::LinearCombination>::CollectiveOp;
-
- using CollectiveMainloop = typename cutlass::gemm::collective::CollectiveBuilder<
- ArchTag,
- OperatorClass,
- ElementA,
- LayoutATag,
- AlignmentA,
- ElementB,
- LayoutBTag,
- AlignmentB,
- ElementAccumulator,
- MmaTileShape,
- ClusterShape,
- cutlass::gemm::collective::StageCountAutoCarveout(
- sizeof(typename CollectiveEpilogue::SharedStorage))>,
- MainloopSchedule>::CollectiveOp;
-
- using GemmKernel =
- cutlass::gemm::kernel::GemmUniversal, CollectiveMainloop, CollectiveEpilogue, void>;
- using Gemm = cutlass::gemm::device::GemmUniversalAdapter;
- using StrideA = typename Gemm::GemmKernel::StrideA;
- using LayoutA = decltype(cute::make_layout(make_shape(0, 0, 0), StrideA{}));
- using LayoutSFA = typename Gemm::GemmKernel::CollectiveMainloop::LayoutSFA;
- using StrideB = typename Gemm::GemmKernel::StrideB;
- using LayoutB = decltype(cute::make_layout(make_shape(0, 0, 0), StrideB{}));
- using LayoutSFB = typename Gemm::GemmKernel::CollectiveMainloop::LayoutSFB;
- using StrideC = typename Gemm::GemmKernel::StrideC;
- using LayoutC = decltype(cute::make_layout(make_shape(0, 0, 0), StrideC{}));
- using StrideD = typename Gemm::GemmKernel::StrideD;
- using LayoutD = decltype(cute::make_layout(make_shape(0, 0, 0), StrideD{}));
-};
-
-template
-typename T::Gemm::Arguments args_from_options(
- tvm::ffi::TensorView D,
- tvm::ffi::TensorView A,
- tvm::ffi::TensorView B,
- tvm::ffi::TensorView A_sf,
- tvm::ffi::TensorView B_sf,
- tvm::ffi::TensorView alpha,
- int64_t M,
- int64_t N,
- int64_t K) {
- using ElementA = typename T::Gemm::ElementA;
- using ElementB = typename T::Gemm::ElementB;
- using ElementSFA = cutlass::float_ue4m3_t;
- using ElementSFB = cutlass::float_ue4m3_t;
- using ElementD = typename T::Gemm::ElementD;
- using ElementCompute = float;
- using StrideA = typename T::StrideA;
- using StrideB = typename T::StrideB;
- using StrideD = typename T::StrideD;
- using Sm1xxBlkScaledConfig = typename T::Gemm::GemmKernel::CollectiveMainloop::Sm1xxBlkScaledConfig;
-
- int m = static_cast(M);
- int n = static_cast(N);
- int k = static_cast(K);
- auto stride_A = cutlass::make_cute_packed_stride(StrideA{}, {m, k, 1});
- auto stride_B = cutlass::make_cute_packed_stride(StrideB{}, {n, k, 1});
- auto stride_D = cutlass::make_cute_packed_stride(StrideD{}, {m, n, 1});
-
- auto layout_SFA = Sm1xxBlkScaledConfig::tile_atom_to_shape_SFA(cute::make_shape(m, n, k, 1));
- auto layout_SFB = Sm1xxBlkScaledConfig::tile_atom_to_shape_SFB(cute::make_shape(m, n, k, 1));
-
- typename T::Gemm::Arguments arguments{
- cutlass::gemm::GemmUniversalMode::kGemm,
- {m, n, k, 1},
- {// Mainloop arguments
- static_cast(A.data_ptr()),
- stride_A,
- static_cast