[Kernel] Decouple KernelBackend from device + device-based CapabilityRequirement (RFC #29630) (#31292)
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
68d324d697
commit
8432eafd3d
@@ -53,7 +53,7 @@ explicitly, e.g.:
|
|||||||
|
|
||||||
```python
|
```python
|
||||||
from sglang.kernels import select_kernel, KernelBackend
|
from sglang.kernels import select_kernel, KernelBackend
|
||||||
jit_rmsnorm = select_kernel("layernorm.rmsnorm", backend=KernelBackend.CUDA_JIT).load()
|
jit_rmsnorm = select_kernel("layernorm.rmsnorm", backend=KernelBackend.JIT).load()
|
||||||
```
|
```
|
||||||
|
|
||||||
## `BaseFusedOp` — the per-operator implementation contract
|
## `BaseFusedOp` — the per-operator implementation contract
|
||||||
@@ -67,7 +67,7 @@ single `forward()`:
|
|||||||
every other backend is checked against.
|
every other backend is checked against.
|
||||||
- `forward_torch_compile` — inherited for free as
|
- `forward_torch_compile` — inherited for free as
|
||||||
`torch.compile(forward_native)`.
|
`torch.compile(forward_native)`.
|
||||||
- `forward_triton` / `forward_cuda_jit` / `forward_cuda_aot` /
|
- `forward_triton` / `forward_jit` / `forward_aot` /
|
||||||
`forward_cute_dsl` / `forward_flashinfer` / `forward_deepgemm` — opt-in
|
`forward_cute_dsl` / `forward_flashinfer` / `forward_deepgemm` — opt-in
|
||||||
overrides. A backend is *available* iff its method is overridden.
|
overrides. A backend is *available* iff its method is overridden.
|
||||||
|
|
||||||
|
|||||||
@@ -38,10 +38,12 @@ from sglang.kernels.registry import KernelRegistry, register_kernel, registry
|
|||||||
from sglang.kernels.selector import get_kernel, select_kernel
|
from sglang.kernels.selector import get_kernel, select_kernel
|
||||||
from sglang.kernels.spec import (
|
from sglang.kernels.spec import (
|
||||||
CapabilityRequirement,
|
CapabilityRequirement,
|
||||||
|
DeviceType,
|
||||||
FormatSignature,
|
FormatSignature,
|
||||||
KernelBackend,
|
KernelBackend,
|
||||||
KernelSpec,
|
KernelSpec,
|
||||||
PlatformInfo,
|
PlatformInfo,
|
||||||
|
capabilities_satisfied,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Importing the operator groups populates the registry (metadata only). Kept
|
# Importing the operator groups populates the registry (metadata only). Kept
|
||||||
@@ -53,6 +55,8 @@ __all__ = [
|
|||||||
"ops",
|
"ops",
|
||||||
"BaseFusedOp",
|
"BaseFusedOp",
|
||||||
"CapabilityRequirement",
|
"CapabilityRequirement",
|
||||||
|
"DeviceType",
|
||||||
|
"capabilities_satisfied",
|
||||||
"FormatSignature",
|
"FormatSignature",
|
||||||
"FusedOpTraceRecord",
|
"FusedOpTraceRecord",
|
||||||
"KernelBackend",
|
"KernelBackend",
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ supports:
|
|||||||
every other backend is checked against.
|
every other backend is checked against.
|
||||||
- ``forward_torch_compile`` — provided by the base class as
|
- ``forward_torch_compile`` — provided by the base class as
|
||||||
``torch.compile(forward_native)``.
|
``torch.compile(forward_native)``.
|
||||||
- ``forward_triton`` / ``forward_cuda_jit`` / ``forward_cuda_aot`` /
|
- ``forward_triton`` / ``forward_jit`` / ``forward_aot`` /
|
||||||
``forward_cute_dsl`` / ``forward_flashinfer`` / ``forward_deepgemm`` —
|
``forward_cute_dsl`` / ``forward_flashinfer`` / ``forward_deepgemm`` —
|
||||||
opt-in overrides.
|
opt-in overrides.
|
||||||
|
|
||||||
@@ -36,7 +36,16 @@ from __future__ import annotations
|
|||||||
|
|
||||||
import functools
|
import functools
|
||||||
from abc import ABC, abstractmethod
|
from abc import ABC, abstractmethod
|
||||||
from typing import Any, ClassVar, Dict, List, Mapping, Optional, Tuple
|
from typing import (
|
||||||
|
AbstractSet,
|
||||||
|
Any,
|
||||||
|
ClassVar,
|
||||||
|
Dict,
|
||||||
|
List,
|
||||||
|
Mapping,
|
||||||
|
Optional,
|
||||||
|
Tuple,
|
||||||
|
)
|
||||||
|
|
||||||
import msgspec
|
import msgspec
|
||||||
|
|
||||||
@@ -47,29 +56,32 @@ from sglang.kernels.spec import (
|
|||||||
KernelBackend,
|
KernelBackend,
|
||||||
KernelSpec,
|
KernelSpec,
|
||||||
PlatformInfo,
|
PlatformInfo,
|
||||||
|
capabilities_satisfied,
|
||||||
)
|
)
|
||||||
|
|
||||||
# backend -> forward_<backend> method name.
|
# backend (provenance) -> forward_<backend> method name.
|
||||||
BACKEND_METHODS: Dict[KernelBackend, str] = {
|
BACKEND_METHODS: Dict[KernelBackend, str] = {
|
||||||
KernelBackend.TORCH: "forward_native",
|
KernelBackend.TORCH: "forward_native",
|
||||||
KernelBackend.TORCH_COMPILE: "forward_torch_compile",
|
KernelBackend.TORCH_COMPILE: "forward_torch_compile",
|
||||||
KernelBackend.TRITON: "forward_triton",
|
KernelBackend.TRITON: "forward_triton",
|
||||||
KernelBackend.CUDA_JIT: "forward_cuda_jit",
|
KernelBackend.JIT: "forward_jit",
|
||||||
KernelBackend.CUDA_AOT: "forward_cuda_aot",
|
KernelBackend.AOT: "forward_aot",
|
||||||
KernelBackend.CUTE_DSL: "forward_cute_dsl",
|
KernelBackend.CUTE_DSL: "forward_cute_dsl",
|
||||||
KernelBackend.FLASHINFER: "forward_flashinfer",
|
KernelBackend.FLASHINFER: "forward_flashinfer",
|
||||||
KernelBackend.DEEPGEMM: "forward_deepgemm",
|
KernelBackend.DEEPGEMM: "forward_deepgemm",
|
||||||
|
KernelBackend.AITER: "forward_aiter",
|
||||||
}
|
}
|
||||||
|
|
||||||
# best -> fallback. ``torch_compile`` is deliberately absent: auto-selection
|
# best -> fallback. ``torch_compile`` is deliberately absent: auto-selection
|
||||||
# must never trigger a surprise compilation in a serving process; force it
|
# must never trigger a surprise compilation in a serving process; force it
|
||||||
# explicitly when wanted.
|
# explicitly when wanted. Per-op priority overrides this (see BaseFusedOp).
|
||||||
DEFAULT_PRIORITY: Tuple[KernelBackend, ...] = (
|
DEFAULT_PRIORITY: Tuple[KernelBackend, ...] = (
|
||||||
KernelBackend.CUDA_AOT,
|
KernelBackend.AOT,
|
||||||
KernelBackend.CUDA_JIT,
|
KernelBackend.JIT,
|
||||||
KernelBackend.FLASHINFER,
|
KernelBackend.FLASHINFER,
|
||||||
KernelBackend.DEEPGEMM,
|
KernelBackend.DEEPGEMM,
|
||||||
KernelBackend.CUTE_DSL,
|
KernelBackend.CUTE_DSL,
|
||||||
|
KernelBackend.AITER,
|
||||||
KernelBackend.TRITON,
|
KernelBackend.TRITON,
|
||||||
KernelBackend.TORCH,
|
KernelBackend.TORCH,
|
||||||
)
|
)
|
||||||
@@ -181,8 +193,11 @@ class BaseFusedOp(ABC):
|
|||||||
Backend preference for auto-selection, best first. Defaults to
|
Backend preference for auto-selection, best first. Defaults to
|
||||||
:data:`DEFAULT_PRIORITY`.
|
:data:`DEFAULT_PRIORITY`.
|
||||||
capabilities:
|
capabilities:
|
||||||
Per-backend :class:`CapabilityRequirement`, consulted by
|
Per-backend set of :class:`CapabilityRequirement` (OR semantics;
|
||||||
:meth:`backend_eligible` (and exported into the registry specs).
|
omitted / empty = runs on any device), consulted by
|
||||||
|
:meth:`backend_eligible` (and exported into the registry specs). Use the
|
||||||
|
``CapabilityRequirement.CUDA`` / ``.HIP`` / ``.NPU`` shortcuts, e.g.
|
||||||
|
``{KernelBackend.AOT: {CapabilityRequirement.CUDA, CapabilityRequirement.HIP}}``.
|
||||||
format_signature:
|
format_signature:
|
||||||
Data-contract description shared by all backends of this op.
|
Data-contract description shared by all backends of this op.
|
||||||
descriptions:
|
descriptions:
|
||||||
@@ -191,7 +206,9 @@ class BaseFusedOp(ABC):
|
|||||||
|
|
||||||
op: ClassVar[str]
|
op: ClassVar[str]
|
||||||
priority: ClassVar[Tuple[KernelBackend, ...]] = DEFAULT_PRIORITY
|
priority: ClassVar[Tuple[KernelBackend, ...]] = DEFAULT_PRIORITY
|
||||||
capabilities: ClassVar[Mapping[KernelBackend, CapabilityRequirement]] = {}
|
capabilities: ClassVar[
|
||||||
|
Mapping[KernelBackend, AbstractSet[CapabilityRequirement]]
|
||||||
|
] = {}
|
||||||
format_signature: ClassVar[FormatSignature] = FormatSignature()
|
format_signature: ClassVar[FormatSignature] = FormatSignature()
|
||||||
descriptions: ClassVar[Mapping[KernelBackend, str]] = {}
|
descriptions: ClassVar[Mapping[KernelBackend, str]] = {}
|
||||||
|
|
||||||
@@ -234,11 +251,11 @@ class BaseFusedOp(ABC):
|
|||||||
def forward_triton(self, *args, **kwargs):
|
def forward_triton(self, *args, **kwargs):
|
||||||
raise NotImplementedError(f"{self.op}: no triton backend")
|
raise NotImplementedError(f"{self.op}: no triton backend")
|
||||||
|
|
||||||
def forward_cuda_jit(self, *args, **kwargs):
|
def forward_jit(self, *args, **kwargs):
|
||||||
raise NotImplementedError(f"{self.op}: no cuda_jit backend")
|
raise NotImplementedError(f"{self.op}: no jit backend")
|
||||||
|
|
||||||
def forward_cuda_aot(self, *args, **kwargs):
|
def forward_aot(self, *args, **kwargs):
|
||||||
raise NotImplementedError(f"{self.op}: no cuda_aot backend")
|
raise NotImplementedError(f"{self.op}: no aot backend")
|
||||||
|
|
||||||
def forward_cute_dsl(self, *args, **kwargs):
|
def forward_cute_dsl(self, *args, **kwargs):
|
||||||
raise NotImplementedError(f"{self.op}: no cute_dsl backend")
|
raise NotImplementedError(f"{self.op}: no cute_dsl backend")
|
||||||
@@ -249,6 +266,9 @@ class BaseFusedOp(ABC):
|
|||||||
def forward_deepgemm(self, *args, **kwargs):
|
def forward_deepgemm(self, *args, **kwargs):
|
||||||
raise NotImplementedError(f"{self.op}: no deepgemm backend")
|
raise NotImplementedError(f"{self.op}: no deepgemm backend")
|
||||||
|
|
||||||
|
def forward_aiter(self, *args, **kwargs):
|
||||||
|
raise NotImplementedError(f"{self.op}: no aiter backend")
|
||||||
|
|
||||||
# --- selection ---
|
# --- selection ---
|
||||||
|
|
||||||
def available_backends(self) -> List[KernelBackend]:
|
def available_backends(self) -> List[KernelBackend]:
|
||||||
@@ -259,12 +279,13 @@ class BaseFusedOp(ABC):
|
|||||||
"""Whether ``backend`` may run *this* call.
|
"""Whether ``backend`` may run *this* call.
|
||||||
|
|
||||||
The base implementation checks the backend's
|
The base implementation checks the backend's
|
||||||
:class:`CapabilityRequirement` against the detected platform.
|
:class:`CapabilityRequirement` set (OR semantics) against the detected
|
||||||
Subclasses may extend it with per-call shape/dtype gates so
|
platform. Subclasses may extend it with per-call shape/dtype gates so
|
||||||
auto-selection bounces to the next backend instead of raising.
|
auto-selection bounces to the next backend instead of raising.
|
||||||
"""
|
"""
|
||||||
capability = self.capabilities.get(backend)
|
return capabilities_satisfied(
|
||||||
return capability is None or capability.is_satisfied_by(_platform())
|
self.capabilities.get(backend, frozenset()), _platform()
|
||||||
|
)
|
||||||
|
|
||||||
def _resolve_backend(self, *args, **kwargs) -> KernelBackend:
|
def _resolve_backend(self, *args, **kwargs) -> KernelBackend:
|
||||||
forced = get_fused_op_backend()
|
forced = get_fused_op_backend()
|
||||||
@@ -300,7 +321,7 @@ def register_fused_op(instance: BaseFusedOp, module: str, attr: str) -> BaseFuse
|
|||||||
|
|
||||||
``module``/``attr`` locate the module-level instance so that
|
``module``/``attr`` locate the module-level instance so that
|
||||||
``KernelSpec.load()`` can lazily resolve e.g.
|
``KernelSpec.load()`` can lazily resolve e.g.
|
||||||
``"<module>:<attr>.forward_cuda_aot"`` to the bound backend method. Returns
|
``"<module>:<attr>.forward_aot"`` to the bound backend method. Returns
|
||||||
``instance`` so group packages can write
|
``instance`` so group packages can write
|
||||||
``_RMSNORM = register_fused_op(_RMSNormOp(), __name__, "_RMSNORM")``.
|
``_RMSNORM = register_fused_op(_RMSNormOp(), __name__, "_RMSNORM")``.
|
||||||
"""
|
"""
|
||||||
@@ -310,7 +331,7 @@ def register_fused_op(instance: BaseFusedOp, module: str, attr: str) -> BaseFuse
|
|||||||
op=instance.op,
|
op=instance.op,
|
||||||
backend=backend,
|
backend=backend,
|
||||||
target=f"{module}:{attr}.{BACKEND_METHODS[backend]}",
|
target=f"{module}:{attr}.{BACKEND_METHODS[backend]}",
|
||||||
capability=instance.capabilities.get(backend, CapabilityRequirement()),
|
capabilities=frozenset(instance.capabilities.get(backend, ())),
|
||||||
format_signature=instance.format_signature,
|
format_signature=instance.format_signature,
|
||||||
description=instance.descriptions.get(backend, ""),
|
description=instance.descriptions.get(backend, ""),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ Each operator is a :class:`~sglang.kernels.fused_op.BaseFusedOp` with a
|
|||||||
pure-``torch`` reference (``forward_native``) plus AOT (``sgl_kernel``) and
|
pure-``torch`` reference (``forward_native``) plus AOT (``sgl_kernel``) and
|
||||||
JIT CUDA backends behind one ``(input, out)`` signature. The JIT backend
|
JIT CUDA backends behind one ``(input, out)`` signature. The JIT backend
|
||||||
additionally accepts ``expert_ids`` / ``expert_step`` — call
|
additionally accepts ``expert_ids`` / ``expert_step`` — call
|
||||||
``forward_cuda_jit`` directly when those are needed.
|
``forward_jit`` directly when those are needed.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
@@ -24,10 +24,17 @@ if TYPE_CHECKING:
|
|||||||
import torch
|
import torch
|
||||||
|
|
||||||
_ACT_DTYPES = ("float16", "bfloat16")
|
_ACT_DTYPES = ("float16", "bfloat16")
|
||||||
_CUDA = CapabilityRequirement(requires_cuda=True)
|
_CUDA = frozenset({CapabilityRequirement.CUDA})
|
||||||
|
_HIP = frozenset({CapabilityRequirement.HIP})
|
||||||
|
# sgl_kernel's gated-activation ops build for CUDA *and* ROCm (production
|
||||||
|
# imports them from sgl_kernel on both), so the AOT backend spans both devices
|
||||||
|
# — the canonical OR-semantics case that a device-baked backend name couldn't.
|
||||||
|
_CUDA_HIP = frozenset({CapabilityRequirement.CUDA, CapabilityRequirement.HIP})
|
||||||
|
# JIT before AOT to match the production path (srt/layers/activation.py imports
|
||||||
|
# from sglang.jit_kernel.activation on CUDA); auto-selection must not invert it.
|
||||||
_ACT_PRIORITY = (
|
_ACT_PRIORITY = (
|
||||||
KernelBackend.CUDA_AOT,
|
KernelBackend.JIT,
|
||||||
KernelBackend.CUDA_JIT,
|
KernelBackend.AOT,
|
||||||
KernelBackend.TORCH,
|
KernelBackend.TORCH,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -40,8 +47,8 @@ class _GatedActivationOp(BaseFusedOp):
|
|||||||
|
|
||||||
priority = _ACT_PRIORITY
|
priority = _ACT_PRIORITY
|
||||||
capabilities = {
|
capabilities = {
|
||||||
KernelBackend.CUDA_AOT: _CUDA,
|
KernelBackend.AOT: _CUDA_HIP,
|
||||||
KernelBackend.CUDA_JIT: _CUDA,
|
KernelBackend.JIT: _CUDA,
|
||||||
}
|
}
|
||||||
format_signature = FormatSignature(
|
format_signature = FormatSignature(
|
||||||
supported_dtypes=_ACT_DTYPES,
|
supported_dtypes=_ACT_DTYPES,
|
||||||
@@ -61,14 +68,14 @@ class _GatedActivationOp(BaseFusedOp):
|
|||||||
out.copy_(result)
|
out.copy_(result)
|
||||||
return out
|
return out
|
||||||
|
|
||||||
def forward_cuda_aot(
|
def forward_aot(
|
||||||
self, input: torch.Tensor, out: Optional[torch.Tensor] = None
|
self, input: torch.Tensor, out: Optional[torch.Tensor] = None
|
||||||
) -> torch.Tensor:
|
) -> torch.Tensor:
|
||||||
import sgl_kernel
|
import sgl_kernel
|
||||||
|
|
||||||
return getattr(sgl_kernel, self.kernel_attr)(input, out)
|
return getattr(sgl_kernel, self.kernel_attr)(input, out)
|
||||||
|
|
||||||
def forward_cuda_jit(
|
def forward_jit(
|
||||||
self,
|
self,
|
||||||
input: torch.Tensor,
|
input: torch.Tensor,
|
||||||
out: Optional[torch.Tensor] = None,
|
out: Optional[torch.Tensor] = None,
|
||||||
@@ -83,13 +90,37 @@ class _GatedActivationOp(BaseFusedOp):
|
|||||||
|
|
||||||
|
|
||||||
class SiluAndMulOp(_GatedActivationOp):
|
class SiluAndMulOp(_GatedActivationOp):
|
||||||
"""``out = silu(input[..., :d]) * input[..., d:]`` with ``d = input.shape[-1] // 2``."""
|
"""``out = silu(input[..., :d]) * input[..., d:]`` with ``d = input.shape[-1] // 2``.
|
||||||
|
|
||||||
|
Adds an ``AITER`` backend on ``device=HIP``: on ROCm this op has a native
|
||||||
|
``aiter`` kernel (``srt/layers/activation.py`` uses it in production). Note
|
||||||
|
the sibling gelu ops below deliberately do *not* register AITER — ROCm/aiter
|
||||||
|
coverage is a per-``(op, backend)`` subset, which the decoupled backend/device
|
||||||
|
model expresses directly (a device-agnostic ``KernelBackend`` name plus a
|
||||||
|
per-backend ``CapabilityRequirement``).
|
||||||
|
"""
|
||||||
|
|
||||||
op = "activation.silu_and_mul"
|
op = "activation.silu_and_mul"
|
||||||
kernel_attr = "silu_and_mul"
|
kernel_attr = "silu_and_mul"
|
||||||
|
# AOT spans CUDA+HIP; JIT is CUDA; AITER is an opt-in HIP path. By priority,
|
||||||
|
# CUDA resolves to JIT and HIP resolves to AOT (matching production
|
||||||
|
# defaults); AITER is registered and HIP-eligible but sits below AOT, so it
|
||||||
|
# is available for explicit/forced selection without changing the default.
|
||||||
|
priority = (
|
||||||
|
KernelBackend.JIT,
|
||||||
|
KernelBackend.AOT,
|
||||||
|
KernelBackend.AITER,
|
||||||
|
KernelBackend.TORCH,
|
||||||
|
)
|
||||||
|
capabilities = {
|
||||||
|
KernelBackend.AOT: _CUDA_HIP,
|
||||||
|
KernelBackend.JIT: _CUDA,
|
||||||
|
KernelBackend.AITER: _HIP,
|
||||||
|
}
|
||||||
descriptions = {
|
descriptions = {
|
||||||
KernelBackend.CUDA_AOT: "silu_and_mul (sgl_kernel wheel).",
|
KernelBackend.AOT: "silu_and_mul (sgl_kernel wheel).",
|
||||||
KernelBackend.CUDA_JIT: "silu_and_mul (sglang.jit_kernel).",
|
KernelBackend.JIT: "silu_and_mul (sglang.jit_kernel).",
|
||||||
|
KernelBackend.AITER: "silu_and_mul (aiter, ROCm).",
|
||||||
KernelBackend.TORCH: "silu_and_mul (pure-torch reference).",
|
KernelBackend.TORCH: "silu_and_mul (pure-torch reference).",
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -98,6 +129,22 @@ class SiluAndMulOp(_GatedActivationOp):
|
|||||||
|
|
||||||
return F.silu(gate)
|
return F.silu(gate)
|
||||||
|
|
||||||
|
def forward_aiter(
|
||||||
|
self, input: torch.Tensor, out: Optional[torch.Tensor] = None
|
||||||
|
) -> torch.Tensor:
|
||||||
|
import torch
|
||||||
|
from aiter import silu_and_mul as _aiter_silu_and_mul
|
||||||
|
|
||||||
|
d = input.shape[-1] // 2
|
||||||
|
if out is None:
|
||||||
|
out = torch.empty(
|
||||||
|
(*input.shape[:-1], d), dtype=input.dtype, device=input.device
|
||||||
|
)
|
||||||
|
# aiter's ROCm silu_and_mul: (out, input, limit); limit=0.0 = no clamp,
|
||||||
|
# matching the standard (unclamped) gated-SiLU used elsewhere.
|
||||||
|
_aiter_silu_and_mul(out, input, 0.0)
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
class GeluAndMulOp(_GatedActivationOp):
|
class GeluAndMulOp(_GatedActivationOp):
|
||||||
"""``out = gelu(input[..., :d]) * input[..., d:]`` (erf-based GELU)."""
|
"""``out = gelu(input[..., :d]) * input[..., d:]`` (erf-based GELU)."""
|
||||||
@@ -105,8 +152,8 @@ class GeluAndMulOp(_GatedActivationOp):
|
|||||||
op = "activation.gelu_and_mul"
|
op = "activation.gelu_and_mul"
|
||||||
kernel_attr = "gelu_and_mul"
|
kernel_attr = "gelu_and_mul"
|
||||||
descriptions = {
|
descriptions = {
|
||||||
KernelBackend.CUDA_AOT: "gelu_and_mul (sgl_kernel wheel).",
|
KernelBackend.AOT: "gelu_and_mul (sgl_kernel wheel).",
|
||||||
KernelBackend.CUDA_JIT: "gelu_and_mul (sglang.jit_kernel).",
|
KernelBackend.JIT: "gelu_and_mul (sglang.jit_kernel).",
|
||||||
KernelBackend.TORCH: "gelu_and_mul (pure-torch reference).",
|
KernelBackend.TORCH: "gelu_and_mul (pure-torch reference).",
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -122,8 +169,8 @@ class GeluTanhAndMulOp(_GatedActivationOp):
|
|||||||
op = "activation.gelu_tanh_and_mul"
|
op = "activation.gelu_tanh_and_mul"
|
||||||
kernel_attr = "gelu_tanh_and_mul"
|
kernel_attr = "gelu_tanh_and_mul"
|
||||||
descriptions = {
|
descriptions = {
|
||||||
KernelBackend.CUDA_AOT: "gelu_tanh_and_mul (sgl_kernel wheel).",
|
KernelBackend.AOT: "gelu_tanh_and_mul (sgl_kernel wheel).",
|
||||||
KernelBackend.CUDA_JIT: "gelu_tanh_and_mul (sglang.jit_kernel).",
|
KernelBackend.JIT: "gelu_tanh_and_mul (sglang.jit_kernel).",
|
||||||
KernelBackend.TORCH: "gelu_tanh_and_mul (pure-torch reference).",
|
KernelBackend.TORCH: "gelu_tanh_and_mul (pure-torch reference).",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -20,14 +20,14 @@ if TYPE_CHECKING:
|
|||||||
import torch
|
import torch
|
||||||
from torch import nn
|
from torch import nn
|
||||||
|
|
||||||
_CUDA = CapabilityRequirement(requires_cuda=True)
|
_CUDA = frozenset({CapabilityRequirement.CUDA})
|
||||||
|
|
||||||
register_kernel(
|
register_kernel(
|
||||||
KernelSpec(
|
KernelSpec(
|
||||||
op="diffusion.apply_group_norm_silu",
|
op="diffusion.apply_group_norm_silu",
|
||||||
backend=KernelBackend.CUDA_JIT,
|
backend=KernelBackend.JIT,
|
||||||
target="sglang.jit_kernel.diffusion.group_norm_silu:apply_group_norm_silu",
|
target="sglang.jit_kernel.diffusion.group_norm_silu:apply_group_norm_silu",
|
||||||
capability=_CUDA,
|
capabilities=_CUDA,
|
||||||
format_signature=FormatSignature(description="fused GroupNorm + SiLU"),
|
format_signature=FormatSignature(description="fused GroupNorm + SiLU"),
|
||||||
description="Fused group-norm + SiLU (sglang.jit_kernel).",
|
description="Fused group-norm + SiLU (sglang.jit_kernel).",
|
||||||
)
|
)
|
||||||
@@ -35,9 +35,9 @@ register_kernel(
|
|||||||
register_kernel(
|
register_kernel(
|
||||||
KernelSpec(
|
KernelSpec(
|
||||||
op="diffusion.residual_gate_add",
|
op="diffusion.residual_gate_add",
|
||||||
backend=KernelBackend.CUDA_JIT,
|
backend=KernelBackend.JIT,
|
||||||
target="sglang.jit_kernel.diffusion.residual_gate_add:residual_gate_add_cuda",
|
target="sglang.jit_kernel.diffusion.residual_gate_add:residual_gate_add_cuda",
|
||||||
capability=_CUDA,
|
capabilities=_CUDA,
|
||||||
format_signature=FormatSignature(description="residual + gate * update"),
|
format_signature=FormatSignature(description="residual + gate * update"),
|
||||||
description="Fused residual gate-add (sglang.jit_kernel).",
|
description="Fused residual gate-add (sglang.jit_kernel).",
|
||||||
)
|
)
|
||||||
@@ -45,9 +45,9 @@ register_kernel(
|
|||||||
register_kernel(
|
register_kernel(
|
||||||
KernelSpec(
|
KernelSpec(
|
||||||
op="diffusion.fused_inplace_qknorm_rope",
|
op="diffusion.fused_inplace_qknorm_rope",
|
||||||
backend=KernelBackend.CUDA_JIT,
|
backend=KernelBackend.JIT,
|
||||||
target="sglang.jit_kernel.diffusion.qknorm_rope:fused_inplace_qknorm_rope",
|
target="sglang.jit_kernel.diffusion.qknorm_rope:fused_inplace_qknorm_rope",
|
||||||
capability=_CUDA,
|
capabilities=_CUDA,
|
||||||
format_signature=FormatSignature(
|
format_signature=FormatSignature(
|
||||||
in_place=True, description="fused in-place QK-norm + RoPE"
|
in_place=True, description="fused in-place QK-norm + RoPE"
|
||||||
),
|
),
|
||||||
@@ -60,7 +60,7 @@ def apply_group_norm_silu(
|
|||||||
x: torch.Tensor, norm: nn.Module, activation: nn.Module
|
x: torch.Tensor, norm: nn.Module, activation: nn.Module
|
||||||
) -> torch.Tensor:
|
) -> torch.Tensor:
|
||||||
"""Fused GroupNorm + SiLU (falls back to eager when unsupported)."""
|
"""Fused GroupNorm + SiLU (falls back to eager when unsupported)."""
|
||||||
return get_kernel("diffusion.apply_group_norm_silu", KernelBackend.CUDA_JIT)(
|
return get_kernel("diffusion.apply_group_norm_silu", KernelBackend.JIT)(
|
||||||
x, norm, activation
|
x, norm, activation
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -69,7 +69,7 @@ def residual_gate_add(
|
|||||||
residual: torch.Tensor, update: torch.Tensor, gate: torch.Tensor
|
residual: torch.Tensor, update: torch.Tensor, gate: torch.Tensor
|
||||||
) -> torch.Tensor:
|
) -> torch.Tensor:
|
||||||
"""Fused ``residual + gate * update``."""
|
"""Fused ``residual + gate * update``."""
|
||||||
return get_kernel("diffusion.residual_gate_add", KernelBackend.CUDA_JIT)(
|
return get_kernel("diffusion.residual_gate_add", KernelBackend.JIT)(
|
||||||
residual, update, gate
|
residual, update, gate
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -88,7 +88,7 @@ def fused_inplace_qknorm_rope(
|
|||||||
rope_dim: int = 0,
|
rope_dim: int = 0,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Fused in-place QK RMS-norm + RoPE."""
|
"""Fused in-place QK RMS-norm + RoPE."""
|
||||||
return get_kernel("diffusion.fused_inplace_qknorm_rope", KernelBackend.CUDA_JIT)(
|
return get_kernel("diffusion.fused_inplace_qknorm_rope", KernelBackend.JIT)(
|
||||||
q,
|
q,
|
||||||
k,
|
k,
|
||||||
q_weight,
|
q_weight,
|
||||||
|
|||||||
@@ -16,12 +16,12 @@ from sglang.kernels.spec import (
|
|||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
import torch
|
import torch
|
||||||
|
|
||||||
_CUDA = CapabilityRequirement(requires_cuda=True)
|
_CUDA = frozenset({CapabilityRequirement.CUDA})
|
||||||
|
|
||||||
register_kernel(
|
register_kernel(
|
||||||
KernelSpec(
|
KernelSpec(
|
||||||
op="gemm.fp8_scaled_mm",
|
op="gemm.fp8_scaled_mm",
|
||||||
backend=KernelBackend.CUDA_AOT,
|
backend=KernelBackend.AOT,
|
||||||
target="sgl_kernel:fp8_scaled_mm",
|
target="sgl_kernel:fp8_scaled_mm",
|
||||||
format_signature=FormatSignature(
|
format_signature=FormatSignature(
|
||||||
supported_dtypes=("float8_e4m3fn",),
|
supported_dtypes=("float8_e4m3fn",),
|
||||||
@@ -33,7 +33,7 @@ register_kernel(
|
|||||||
register_kernel(
|
register_kernel(
|
||||||
KernelSpec(
|
KernelSpec(
|
||||||
op="gemm.dsv3_fused_a_gemm",
|
op="gemm.dsv3_fused_a_gemm",
|
||||||
backend=KernelBackend.CUDA_AOT,
|
backend=KernelBackend.AOT,
|
||||||
target="sgl_kernel:dsv3_fused_a_gemm",
|
target="sgl_kernel:dsv3_fused_a_gemm",
|
||||||
format_signature=FormatSignature(
|
format_signature=FormatSignature(
|
||||||
supported_dtypes=("bfloat16",),
|
supported_dtypes=("bfloat16",),
|
||||||
@@ -45,9 +45,9 @@ register_kernel(
|
|||||||
register_kernel(
|
register_kernel(
|
||||||
KernelSpec(
|
KernelSpec(
|
||||||
op="gemm.dsv3_fused_a_gemm",
|
op="gemm.dsv3_fused_a_gemm",
|
||||||
backend=KernelBackend.CUDA_JIT,
|
backend=KernelBackend.JIT,
|
||||||
target="sglang.jit_kernel.dsv3_fused_a_gemm:dsv3_fused_a_gemm",
|
target="sglang.jit_kernel.dsv3_fused_a_gemm:dsv3_fused_a_gemm",
|
||||||
capability=_CUDA,
|
capabilities=_CUDA,
|
||||||
format_signature=FormatSignature(
|
format_signature=FormatSignature(
|
||||||
supported_dtypes=("bfloat16",),
|
supported_dtypes=("bfloat16",),
|
||||||
description="DeepSeek-V3 fused QKV-A GEMM (drop-in with AOT signature)",
|
description="DeepSeek-V3 fused QKV-A GEMM (drop-in with AOT signature)",
|
||||||
@@ -58,9 +58,9 @@ register_kernel(
|
|||||||
register_kernel(
|
register_kernel(
|
||||||
KernelSpec(
|
KernelSpec(
|
||||||
op="gemm.dsv3_router_gemm",
|
op="gemm.dsv3_router_gemm",
|
||||||
backend=KernelBackend.CUDA_JIT,
|
backend=KernelBackend.JIT,
|
||||||
target="sglang.jit_kernel.dsv3_router_gemm:dsv3_router_gemm",
|
target="sglang.jit_kernel.dsv3_router_gemm:dsv3_router_gemm",
|
||||||
capability=_CUDA,
|
capabilities=_CUDA,
|
||||||
format_signature=FormatSignature(
|
format_signature=FormatSignature(
|
||||||
supported_dtypes=("bfloat16",),
|
supported_dtypes=("bfloat16",),
|
||||||
description="DeepSeek-V3 router GEMM; num_tokens in [1, 16]",
|
description="DeepSeek-V3 router GEMM; num_tokens in [1, 16]",
|
||||||
@@ -79,7 +79,7 @@ def fp8_scaled_mm(
|
|||||||
bias: Optional[torch.Tensor] = None,
|
bias: Optional[torch.Tensor] = None,
|
||||||
) -> torch.Tensor:
|
) -> torch.Tensor:
|
||||||
"""FP8 scaled matmul: ``(mat_a @ mat_b) * scales_a * scales_b (+ bias)``."""
|
"""FP8 scaled matmul: ``(mat_a @ mat_b) * scales_a * scales_b (+ bias)``."""
|
||||||
return get_kernel("gemm.fp8_scaled_mm", KernelBackend.CUDA_AOT)(
|
return get_kernel("gemm.fp8_scaled_mm", KernelBackend.AOT)(
|
||||||
mat_a, mat_b, scales_a, scales_b, out_dtype, bias
|
mat_a, mat_b, scales_a, scales_b, out_dtype, bias
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -90,9 +90,7 @@ def dsv3_fused_a_gemm(
|
|||||||
output: Optional[torch.Tensor] = None,
|
output: Optional[torch.Tensor] = None,
|
||||||
) -> torch.Tensor:
|
) -> torch.Tensor:
|
||||||
"""DeepSeek-V3 fused QKV-A GEMM."""
|
"""DeepSeek-V3 fused QKV-A GEMM."""
|
||||||
return get_kernel("gemm.dsv3_fused_a_gemm", KernelBackend.CUDA_AOT)(
|
return get_kernel("gemm.dsv3_fused_a_gemm", KernelBackend.AOT)(mat_a, mat_b, output)
|
||||||
mat_a, mat_b, output
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def dsv3_router_gemm(
|
def dsv3_router_gemm(
|
||||||
@@ -102,7 +100,7 @@ def dsv3_router_gemm(
|
|||||||
output: Optional[torch.Tensor] = None,
|
output: Optional[torch.Tensor] = None,
|
||||||
) -> torch.Tensor:
|
) -> torch.Tensor:
|
||||||
"""DeepSeek-V3 router GEMM (JIT-backed). ``out_dtype`` defaults to bfloat16."""
|
"""DeepSeek-V3 router GEMM (JIT-backed). ``out_dtype`` defaults to bfloat16."""
|
||||||
impl = get_kernel("gemm.dsv3_router_gemm", KernelBackend.CUDA_JIT)
|
impl = get_kernel("gemm.dsv3_router_gemm", KernelBackend.JIT)
|
||||||
if out_dtype is None:
|
if out_dtype is None:
|
||||||
return impl(hidden_states, router_weights, output=output)
|
return impl(hidden_states, router_weights, output=output)
|
||||||
return impl(hidden_states, router_weights, out_dtype, output)
|
return impl(hidden_states, router_weights, out_dtype, output)
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ all behind one signature. The public module-level functions are thin wrappers
|
|||||||
over module-level instances; auto-selection prefers the AOT ``sgl_kernel``
|
over module-level instances; auto-selection prefers the AOT ``sgl_kernel``
|
||||||
implementation on CUDA and falls back to the native reference elsewhere.
|
implementation on CUDA and falls back to the native reference elsewhere.
|
||||||
Pick a specific backend with e.g.
|
Pick a specific backend with e.g.
|
||||||
``_RMSNORM.forward(x, w, backend=KernelBackend.CUDA_JIT)`` or globally via
|
``_RMSNORM.forward(x, w, backend=KernelBackend.JIT)`` or globally via
|
||||||
``SGLANG_FORCE_FUSED_OP_BACKEND``.
|
``SGLANG_FORCE_FUSED_OP_BACKEND``.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@@ -25,10 +25,10 @@ if TYPE_CHECKING:
|
|||||||
import torch
|
import torch
|
||||||
|
|
||||||
_NORM_DTYPES = ("float16", "bfloat16")
|
_NORM_DTYPES = ("float16", "bfloat16")
|
||||||
_CUDA = CapabilityRequirement(requires_cuda=True)
|
_CUDA = frozenset({CapabilityRequirement.CUDA})
|
||||||
_NORM_PRIORITY = (
|
_NORM_PRIORITY = (
|
||||||
KernelBackend.CUDA_AOT,
|
KernelBackend.AOT,
|
||||||
KernelBackend.CUDA_JIT,
|
KernelBackend.JIT,
|
||||||
KernelBackend.TORCH,
|
KernelBackend.TORCH,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -42,16 +42,16 @@ class RMSNormOp(BaseFusedOp):
|
|||||||
op = "layernorm.rmsnorm"
|
op = "layernorm.rmsnorm"
|
||||||
priority = _NORM_PRIORITY
|
priority = _NORM_PRIORITY
|
||||||
capabilities = {
|
capabilities = {
|
||||||
KernelBackend.CUDA_AOT: _CUDA,
|
KernelBackend.AOT: _CUDA,
|
||||||
KernelBackend.CUDA_JIT: _CUDA,
|
KernelBackend.JIT: _CUDA,
|
||||||
}
|
}
|
||||||
format_signature = FormatSignature(
|
format_signature = FormatSignature(
|
||||||
supported_dtypes=_NORM_DTYPES,
|
supported_dtypes=_NORM_DTYPES,
|
||||||
description="out = (x / RMS(x)) * weight; returns tensor",
|
description="out = (x / RMS(x)) * weight; returns tensor",
|
||||||
)
|
)
|
||||||
descriptions = {
|
descriptions = {
|
||||||
KernelBackend.CUDA_AOT: "RMS normalization (sgl_kernel wheel).",
|
KernelBackend.AOT: "RMS normalization (sgl_kernel wheel).",
|
||||||
KernelBackend.CUDA_JIT: "RMS normalization (sglang.jit_kernel).",
|
KernelBackend.JIT: "RMS normalization (sglang.jit_kernel).",
|
||||||
KernelBackend.TORCH: "RMS normalization (pure-torch reference).",
|
KernelBackend.TORCH: "RMS normalization (pure-torch reference).",
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -74,7 +74,7 @@ class RMSNormOp(BaseFusedOp):
|
|||||||
out.copy_(result)
|
out.copy_(result)
|
||||||
return out
|
return out
|
||||||
|
|
||||||
def forward_cuda_aot(
|
def forward_aot(
|
||||||
self,
|
self,
|
||||||
input: torch.Tensor,
|
input: torch.Tensor,
|
||||||
weight: torch.Tensor,
|
weight: torch.Tensor,
|
||||||
@@ -86,7 +86,7 @@ class RMSNormOp(BaseFusedOp):
|
|||||||
|
|
||||||
return sgl_kernel.rmsnorm(input, weight, eps, out, enable_pdl)
|
return sgl_kernel.rmsnorm(input, weight, eps, out, enable_pdl)
|
||||||
|
|
||||||
def forward_cuda_jit(
|
def forward_jit(
|
||||||
self,
|
self,
|
||||||
input: torch.Tensor,
|
input: torch.Tensor,
|
||||||
weight: torch.Tensor,
|
weight: torch.Tensor,
|
||||||
@@ -114,8 +114,8 @@ class FusedAddRMSNormOp(BaseFusedOp):
|
|||||||
op = "layernorm.fused_add_rmsnorm"
|
op = "layernorm.fused_add_rmsnorm"
|
||||||
priority = _NORM_PRIORITY
|
priority = _NORM_PRIORITY
|
||||||
capabilities = {
|
capabilities = {
|
||||||
KernelBackend.CUDA_AOT: _CUDA,
|
KernelBackend.AOT: _CUDA,
|
||||||
KernelBackend.CUDA_JIT: _CUDA,
|
KernelBackend.JIT: _CUDA,
|
||||||
}
|
}
|
||||||
format_signature = FormatSignature(
|
format_signature = FormatSignature(
|
||||||
supported_dtypes=_NORM_DTYPES,
|
supported_dtypes=_NORM_DTYPES,
|
||||||
@@ -123,10 +123,10 @@ class FusedAddRMSNormOp(BaseFusedOp):
|
|||||||
description="residual += x; x = RMSNorm(residual) * weight",
|
description="residual += x; x = RMSNorm(residual) * weight",
|
||||||
)
|
)
|
||||||
descriptions = {
|
descriptions = {
|
||||||
KernelBackend.CUDA_AOT: (
|
KernelBackend.AOT: (
|
||||||
"Fused residual-add + RMS normalization (sgl_kernel wheel)."
|
"Fused residual-add + RMS normalization (sgl_kernel wheel)."
|
||||||
),
|
),
|
||||||
KernelBackend.CUDA_JIT: (
|
KernelBackend.JIT: (
|
||||||
"Fused residual-add + RMS normalization (sglang.jit_kernel)."
|
"Fused residual-add + RMS normalization (sglang.jit_kernel)."
|
||||||
),
|
),
|
||||||
KernelBackend.TORCH: (
|
KernelBackend.TORCH: (
|
||||||
@@ -150,7 +150,7 @@ class FusedAddRMSNormOp(BaseFusedOp):
|
|||||||
normed = acc * torch.rsqrt(variance + eps)
|
normed = acc * torch.rsqrt(variance + eps)
|
||||||
input.copy_((normed * weight).to(input.dtype))
|
input.copy_((normed * weight).to(input.dtype))
|
||||||
|
|
||||||
def forward_cuda_aot(
|
def forward_aot(
|
||||||
self,
|
self,
|
||||||
input: torch.Tensor,
|
input: torch.Tensor,
|
||||||
residual: torch.Tensor,
|
residual: torch.Tensor,
|
||||||
@@ -162,7 +162,7 @@ class FusedAddRMSNormOp(BaseFusedOp):
|
|||||||
|
|
||||||
return sgl_kernel.fused_add_rmsnorm(input, residual, weight, eps, enable_pdl)
|
return sgl_kernel.fused_add_rmsnorm(input, residual, weight, eps, enable_pdl)
|
||||||
|
|
||||||
def forward_cuda_jit(
|
def forward_jit(
|
||||||
self,
|
self,
|
||||||
input: torch.Tensor,
|
input: torch.Tensor,
|
||||||
residual: torch.Tensor,
|
residual: torch.Tensor,
|
||||||
@@ -180,13 +180,13 @@ class GemmaRMSNormOp(BaseFusedOp):
|
|||||||
|
|
||||||
op = "layernorm.gemma_rmsnorm"
|
op = "layernorm.gemma_rmsnorm"
|
||||||
priority = _NORM_PRIORITY
|
priority = _NORM_PRIORITY
|
||||||
capabilities = {KernelBackend.CUDA_AOT: _CUDA}
|
capabilities = {KernelBackend.AOT: _CUDA}
|
||||||
format_signature = FormatSignature(
|
format_signature = FormatSignature(
|
||||||
supported_dtypes=_NORM_DTYPES,
|
supported_dtypes=_NORM_DTYPES,
|
||||||
description="out = (x / RMS(x)) * (weight + 1); returns tensor",
|
description="out = (x / RMS(x)) * (weight + 1); returns tensor",
|
||||||
)
|
)
|
||||||
descriptions = {
|
descriptions = {
|
||||||
KernelBackend.CUDA_AOT: "Gemma-style RMS normalization (sgl_kernel wheel).",
|
KernelBackend.AOT: "Gemma-style RMS normalization (sgl_kernel wheel).",
|
||||||
KernelBackend.TORCH: "Gemma-style RMS normalization (pure-torch reference).",
|
KernelBackend.TORCH: "Gemma-style RMS normalization (pure-torch reference).",
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -209,7 +209,7 @@ class GemmaRMSNormOp(BaseFusedOp):
|
|||||||
out.copy_(result)
|
out.copy_(result)
|
||||||
return out
|
return out
|
||||||
|
|
||||||
def forward_cuda_aot(
|
def forward_aot(
|
||||||
self,
|
self,
|
||||||
input: torch.Tensor,
|
input: torch.Tensor,
|
||||||
weight: torch.Tensor,
|
weight: torch.Tensor,
|
||||||
@@ -227,14 +227,14 @@ class GemmaFusedAddRMSNormOp(BaseFusedOp):
|
|||||||
|
|
||||||
op = "layernorm.gemma_fused_add_rmsnorm"
|
op = "layernorm.gemma_fused_add_rmsnorm"
|
||||||
priority = _NORM_PRIORITY
|
priority = _NORM_PRIORITY
|
||||||
capabilities = {KernelBackend.CUDA_AOT: _CUDA}
|
capabilities = {KernelBackend.AOT: _CUDA}
|
||||||
format_signature = FormatSignature(
|
format_signature = FormatSignature(
|
||||||
supported_dtypes=_NORM_DTYPES,
|
supported_dtypes=_NORM_DTYPES,
|
||||||
in_place=True,
|
in_place=True,
|
||||||
description="residual += x; x = GemmaRMSNorm(residual) * (weight + 1)",
|
description="residual += x; x = GemmaRMSNorm(residual) * (weight + 1)",
|
||||||
)
|
)
|
||||||
descriptions = {
|
descriptions = {
|
||||||
KernelBackend.CUDA_AOT: ("Gemma-style fused residual-add + RMS normalization."),
|
KernelBackend.AOT: ("Gemma-style fused residual-add + RMS normalization."),
|
||||||
KernelBackend.TORCH: (
|
KernelBackend.TORCH: (
|
||||||
"Gemma-style fused residual-add + RMS normalization "
|
"Gemma-style fused residual-add + RMS normalization "
|
||||||
"(pure-torch reference)."
|
"(pure-torch reference)."
|
||||||
@@ -257,7 +257,7 @@ class GemmaFusedAddRMSNormOp(BaseFusedOp):
|
|||||||
normed = acc * torch.rsqrt(variance + eps)
|
normed = acc * torch.rsqrt(variance + eps)
|
||||||
input.copy_((normed * (1.0 + weight.to(torch.float32))).to(input.dtype))
|
input.copy_((normed * (1.0 + weight.to(torch.float32))).to(input.dtype))
|
||||||
|
|
||||||
def forward_cuda_aot(
|
def forward_aot(
|
||||||
self,
|
self,
|
||||||
input: torch.Tensor,
|
input: torch.Tensor,
|
||||||
residual: torch.Tensor,
|
residual: torch.Tensor,
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ if TYPE_CHECKING:
|
|||||||
register_kernel(
|
register_kernel(
|
||||||
KernelSpec(
|
KernelSpec(
|
||||||
op="mamba.causal_conv1d_fwd",
|
op="mamba.causal_conv1d_fwd",
|
||||||
backend=KernelBackend.CUDA_AOT,
|
backend=KernelBackend.AOT,
|
||||||
target="sgl_kernel.mamba:causal_conv1d_fwd",
|
target="sgl_kernel.mamba:causal_conv1d_fwd",
|
||||||
format_signature=FormatSignature(
|
format_signature=FormatSignature(
|
||||||
in_place=True, description="causal depthwise conv1d forward (prefill)"
|
in_place=True, description="causal depthwise conv1d forward (prefill)"
|
||||||
@@ -25,7 +25,7 @@ register_kernel(
|
|||||||
register_kernel(
|
register_kernel(
|
||||||
KernelSpec(
|
KernelSpec(
|
||||||
op="mamba.causal_conv1d_update",
|
op="mamba.causal_conv1d_update",
|
||||||
backend=KernelBackend.CUDA_AOT,
|
backend=KernelBackend.AOT,
|
||||||
target="sgl_kernel.mamba:causal_conv1d_update",
|
target="sgl_kernel.mamba:causal_conv1d_update",
|
||||||
format_signature=FormatSignature(
|
format_signature=FormatSignature(
|
||||||
in_place=True, description="causal depthwise conv1d update (decode)"
|
in_place=True, description="causal depthwise conv1d update (decode)"
|
||||||
@@ -47,7 +47,7 @@ def causal_conv1d_fwd(
|
|||||||
pad_slot_id: int,
|
pad_slot_id: int,
|
||||||
):
|
):
|
||||||
"""Causal depthwise conv1d forward (prefill)."""
|
"""Causal depthwise conv1d forward (prefill)."""
|
||||||
return get_kernel("mamba.causal_conv1d_fwd", KernelBackend.CUDA_AOT)(
|
return get_kernel("mamba.causal_conv1d_fwd", KernelBackend.AOT)(
|
||||||
x,
|
x,
|
||||||
weight,
|
weight,
|
||||||
bias_,
|
bias_,
|
||||||
@@ -71,7 +71,7 @@ def causal_conv1d_update(
|
|||||||
pad_slot_id: int,
|
pad_slot_id: int,
|
||||||
):
|
):
|
||||||
"""Causal depthwise conv1d update (decode)."""
|
"""Causal depthwise conv1d update (decode)."""
|
||||||
return get_kernel("mamba.causal_conv1d_update", KernelBackend.CUDA_AOT)(
|
return get_kernel("mamba.causal_conv1d_update", KernelBackend.AOT)(
|
||||||
x,
|
x,
|
||||||
conv_state,
|
conv_state,
|
||||||
weight,
|
weight,
|
||||||
|
|||||||
@@ -16,12 +16,12 @@ from sglang.kernels.spec import (
|
|||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
import torch
|
import torch
|
||||||
|
|
||||||
_CUDA = CapabilityRequirement(requires_cuda=True)
|
_CUDA = frozenset({CapabilityRequirement.CUDA})
|
||||||
|
|
||||||
register_kernel(
|
register_kernel(
|
||||||
KernelSpec(
|
KernelSpec(
|
||||||
op="moe.moe_align_block_size",
|
op="moe.moe_align_block_size",
|
||||||
backend=KernelBackend.CUDA_AOT,
|
backend=KernelBackend.AOT,
|
||||||
target="sgl_kernel:moe_align_block_size",
|
target="sgl_kernel:moe_align_block_size",
|
||||||
format_signature=FormatSignature(
|
format_signature=FormatSignature(
|
||||||
in_place=True,
|
in_place=True,
|
||||||
@@ -33,9 +33,9 @@ register_kernel(
|
|||||||
register_kernel(
|
register_kernel(
|
||||||
KernelSpec(
|
KernelSpec(
|
||||||
op="moe.moe_align_block_size",
|
op="moe.moe_align_block_size",
|
||||||
backend=KernelBackend.CUDA_JIT,
|
backend=KernelBackend.JIT,
|
||||||
target="sglang.jit_kernel.moe_align:moe_align_block_size",
|
target="sglang.jit_kernel.moe_align:moe_align_block_size",
|
||||||
capability=_CUDA,
|
capabilities=_CUDA,
|
||||||
format_signature=FormatSignature(
|
format_signature=FormatSignature(
|
||||||
in_place=True,
|
in_place=True,
|
||||||
description="MoE align-block-size (JIT variant, AOT signature)",
|
description="MoE align-block-size (JIT variant, AOT signature)",
|
||||||
@@ -46,7 +46,7 @@ register_kernel(
|
|||||||
register_kernel(
|
register_kernel(
|
||||||
KernelSpec(
|
KernelSpec(
|
||||||
op="moe.topk_softmax",
|
op="moe.topk_softmax",
|
||||||
backend=KernelBackend.CUDA_AOT,
|
backend=KernelBackend.AOT,
|
||||||
target="sgl_kernel:topk_softmax",
|
target="sgl_kernel:topk_softmax",
|
||||||
format_signature=FormatSignature(
|
format_signature=FormatSignature(
|
||||||
in_place=True,
|
in_place=True,
|
||||||
@@ -69,7 +69,7 @@ def moe_align_block_size(
|
|||||||
ignore_invalid_expert: bool = False,
|
ignore_invalid_expert: bool = False,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Align and sort expert token ids into block-padded output buffers."""
|
"""Align and sort expert token ids into block-padded output buffers."""
|
||||||
kernel = get_kernel("moe.moe_align_block_size", KernelBackend.CUDA_AOT)
|
kernel = get_kernel("moe.moe_align_block_size", KernelBackend.AOT)
|
||||||
if ignore_invalid_expert:
|
if ignore_invalid_expert:
|
||||||
return kernel(
|
return kernel(
|
||||||
topk_ids,
|
topk_ids,
|
||||||
@@ -103,7 +103,7 @@ def topk_softmax(
|
|||||||
correction_bias: Optional[torch.Tensor] = None,
|
correction_bias: Optional[torch.Tensor] = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Compute top-k softmax routing weights/ids for MoE."""
|
"""Compute top-k softmax routing weights/ids for MoE."""
|
||||||
return get_kernel("moe.topk_softmax", KernelBackend.CUDA_AOT)(
|
return get_kernel("moe.topk_softmax", KernelBackend.AOT)(
|
||||||
topk_weights,
|
topk_weights,
|
||||||
topk_ids,
|
topk_ids,
|
||||||
gating_output,
|
gating_output,
|
||||||
|
|||||||
@@ -16,12 +16,12 @@ from sglang.kernels.spec import (
|
|||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
import torch
|
import torch
|
||||||
|
|
||||||
_CUDA = CapabilityRequirement(requires_cuda=True)
|
_CUDA = frozenset({CapabilityRequirement.CUDA})
|
||||||
|
|
||||||
register_kernel(
|
register_kernel(
|
||||||
KernelSpec(
|
KernelSpec(
|
||||||
op="quantization.sgl_per_token_quant_fp8",
|
op="quantization.sgl_per_token_quant_fp8",
|
||||||
backend=KernelBackend.CUDA_AOT,
|
backend=KernelBackend.AOT,
|
||||||
target="sgl_kernel:sgl_per_token_quant_fp8",
|
target="sgl_kernel:sgl_per_token_quant_fp8",
|
||||||
format_signature=FormatSignature(
|
format_signature=FormatSignature(
|
||||||
supported_dtypes=("float8_e4m3fn",),
|
supported_dtypes=("float8_e4m3fn",),
|
||||||
@@ -41,7 +41,7 @@ for _name in (
|
|||||||
register_kernel(
|
register_kernel(
|
||||||
KernelSpec(
|
KernelSpec(
|
||||||
op=f"quantization.{_name}",
|
op=f"quantization.{_name}",
|
||||||
backend=KernelBackend.CUDA_AOT,
|
backend=KernelBackend.AOT,
|
||||||
target=f"sgl_kernel:{_name}",
|
target=f"sgl_kernel:{_name}",
|
||||||
format_signature=FormatSignature(
|
format_signature=FormatSignature(
|
||||||
in_place=True,
|
in_place=True,
|
||||||
@@ -55,9 +55,9 @@ del _name
|
|||||||
register_kernel(
|
register_kernel(
|
||||||
KernelSpec(
|
KernelSpec(
|
||||||
op="quantization.sgl_per_token_group_quant_8bit",
|
op="quantization.sgl_per_token_group_quant_8bit",
|
||||||
backend=KernelBackend.CUDA_JIT,
|
backend=KernelBackend.JIT,
|
||||||
target="sglang.jit_kernel.per_token_group_quant_8bit:per_token_group_quant_8bit",
|
target="sglang.jit_kernel.per_token_group_quant_8bit:per_token_group_quant_8bit",
|
||||||
capability=_CUDA,
|
capabilities=_CUDA,
|
||||||
format_signature=FormatSignature(
|
format_signature=FormatSignature(
|
||||||
in_place=True,
|
in_place=True,
|
||||||
description="per-token-group 8-bit quantization (JIT variant)",
|
description="per-token-group 8-bit quantization (JIT variant)",
|
||||||
@@ -73,7 +73,7 @@ def sgl_per_token_quant_fp8(
|
|||||||
output_s: torch.Tensor,
|
output_s: torch.Tensor,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Per-token FP8 quantization, writing into ``output_q`` / ``output_s``."""
|
"""Per-token FP8 quantization, writing into ``output_q`` / ``output_s``."""
|
||||||
return get_kernel("quantization.sgl_per_token_quant_fp8", KernelBackend.CUDA_AOT)(
|
return get_kernel("quantization.sgl_per_token_quant_fp8", KernelBackend.AOT)(
|
||||||
input, output_q, output_s
|
input, output_q, output_s
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -92,9 +92,7 @@ def sgl_per_token_group_quant_8bit(
|
|||||||
enable_v2: Optional[bool] = None,
|
enable_v2: Optional[bool] = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Per-token-group 8-bit quantization, writing into ``output_q`` / ``output_s``."""
|
"""Per-token-group 8-bit quantization, writing into ``output_q`` / ``output_s``."""
|
||||||
return get_kernel(
|
return get_kernel("quantization.sgl_per_token_group_quant_8bit", KernelBackend.AOT)(
|
||||||
"quantization.sgl_per_token_group_quant_8bit", KernelBackend.CUDA_AOT
|
|
||||||
)(
|
|
||||||
input,
|
input,
|
||||||
output_q,
|
output_q,
|
||||||
output_s,
|
output_s,
|
||||||
@@ -163,7 +161,7 @@ register_kernel(
|
|||||||
"sglang.kernels.ops.quantization.nvfp4_gemm_swiglu_nvfp4_quant"
|
"sglang.kernels.ops.quantization.nvfp4_gemm_swiglu_nvfp4_quant"
|
||||||
":nvfp4_gemm_swiglu_nvfp4_quant"
|
":nvfp4_gemm_swiglu_nvfp4_quant"
|
||||||
),
|
),
|
||||||
capability=CapabilityRequirement(requires_cuda=True, min_cuda_arch=(10, 0)),
|
capabilities=frozenset({CapabilityRequirement.cuda(min_sm=(10, 0))}),
|
||||||
description="Fused NVFP4 GEMM + SwiGLU + NVFP4 quant (CuTe DSL, SM100).",
|
description="Fused NVFP4 GEMM + SwiGLU + NVFP4 quant (CuTe DSL, SM100).",
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ if TYPE_CHECKING:
|
|||||||
register_kernel(
|
register_kernel(
|
||||||
KernelSpec(
|
KernelSpec(
|
||||||
op="sampling.top_k_renorm_probs",
|
op="sampling.top_k_renorm_probs",
|
||||||
backend=KernelBackend.CUDA_AOT,
|
backend=KernelBackend.AOT,
|
||||||
target="sgl_kernel.sampling:top_k_renorm_probs",
|
target="sgl_kernel.sampling:top_k_renorm_probs",
|
||||||
format_signature=FormatSignature(
|
format_signature=FormatSignature(
|
||||||
description="renormalize probs by top-k thresholding; returns tensor"
|
description="renormalize probs by top-k thresholding; returns tensor"
|
||||||
@@ -25,7 +25,7 @@ register_kernel(
|
|||||||
register_kernel(
|
register_kernel(
|
||||||
KernelSpec(
|
KernelSpec(
|
||||||
op="sampling.top_p_renorm_probs",
|
op="sampling.top_p_renorm_probs",
|
||||||
backend=KernelBackend.CUDA_AOT,
|
backend=KernelBackend.AOT,
|
||||||
target="sgl_kernel.sampling:top_p_renorm_probs",
|
target="sgl_kernel.sampling:top_p_renorm_probs",
|
||||||
format_signature=FormatSignature(
|
format_signature=FormatSignature(
|
||||||
description="renormalize probs by top-p thresholding; returns tensor"
|
description="renormalize probs by top-p thresholding; returns tensor"
|
||||||
@@ -39,18 +39,14 @@ def top_k_renorm_probs(
|
|||||||
probs: torch.Tensor, top_k: Union[torch.Tensor, int]
|
probs: torch.Tensor, top_k: Union[torch.Tensor, int]
|
||||||
) -> torch.Tensor:
|
) -> torch.Tensor:
|
||||||
"""Renormalize ``probs`` by top-k thresholding."""
|
"""Renormalize ``probs`` by top-k thresholding."""
|
||||||
return get_kernel("sampling.top_k_renorm_probs", KernelBackend.CUDA_AOT)(
|
return get_kernel("sampling.top_k_renorm_probs", KernelBackend.AOT)(probs, top_k)
|
||||||
probs, top_k
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def top_p_renorm_probs(
|
def top_p_renorm_probs(
|
||||||
probs: torch.Tensor, top_p: Union[torch.Tensor, float]
|
probs: torch.Tensor, top_p: Union[torch.Tensor, float]
|
||||||
) -> torch.Tensor:
|
) -> torch.Tensor:
|
||||||
"""Renormalize ``probs`` by top-p thresholding."""
|
"""Renormalize ``probs`` by top-p thresholding."""
|
||||||
return get_kernel("sampling.top_p_renorm_probs", KernelBackend.CUDA_AOT)(
|
return get_kernel("sampling.top_p_renorm_probs", KernelBackend.AOT)(probs, top_p)
|
||||||
probs, top_p
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
__all__ = ["top_k_renorm_probs", "top_p_renorm_probs"]
|
__all__ = ["top_k_renorm_probs", "top_p_renorm_probs"]
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ from sglang.kernels.spec import FormatSignature, KernelBackend, KernelSpec
|
|||||||
register_kernel(
|
register_kernel(
|
||||||
KernelSpec(
|
KernelSpec(
|
||||||
op="spatial.get_sm_available",
|
op="spatial.get_sm_available",
|
||||||
backend=KernelBackend.CUDA_AOT,
|
backend=KernelBackend.AOT,
|
||||||
target="sgl_kernel.spatial:get_sm_available",
|
target="sgl_kernel.spatial:get_sm_available",
|
||||||
format_signature=FormatSignature(
|
format_signature=FormatSignature(
|
||||||
description="number of SMs available on device"
|
description="number of SMs available on device"
|
||||||
@@ -22,7 +22,7 @@ register_kernel(
|
|||||||
register_kernel(
|
register_kernel(
|
||||||
KernelSpec(
|
KernelSpec(
|
||||||
op="spatial.create_greenctx_stream_by_value",
|
op="spatial.create_greenctx_stream_by_value",
|
||||||
backend=KernelBackend.CUDA_AOT,
|
backend=KernelBackend.AOT,
|
||||||
target="sgl_kernel.spatial:create_greenctx_stream_by_value",
|
target="sgl_kernel.spatial:create_greenctx_stream_by_value",
|
||||||
format_signature=FormatSignature(
|
format_signature=FormatSignature(
|
||||||
description="create two green-context streams partitioned by SM count"
|
description="create two green-context streams partitioned by SM count"
|
||||||
@@ -34,16 +34,16 @@ register_kernel(
|
|||||||
|
|
||||||
def get_sm_available(device_id: Optional[int] = None) -> int:
|
def get_sm_available(device_id: Optional[int] = None) -> int:
|
||||||
"""Return the number of SMs available on ``device_id``."""
|
"""Return the number of SMs available on ``device_id``."""
|
||||||
return get_kernel("spatial.get_sm_available", KernelBackend.CUDA_AOT)(device_id)
|
return get_kernel("spatial.get_sm_available", KernelBackend.AOT)(device_id)
|
||||||
|
|
||||||
|
|
||||||
def create_greenctx_stream_by_value(
|
def create_greenctx_stream_by_value(
|
||||||
SM_a: int, SM_b: int, device_id: Optional[int] = None
|
SM_a: int, SM_b: int, device_id: Optional[int] = None
|
||||||
):
|
):
|
||||||
"""Create two green-context streams partitioned by ``SM_a`` / ``SM_b``."""
|
"""Create two green-context streams partitioned by ``SM_a`` / ``SM_b``."""
|
||||||
return get_kernel(
|
return get_kernel("spatial.create_greenctx_stream_by_value", KernelBackend.AOT)(
|
||||||
"spatial.create_greenctx_stream_by_value", KernelBackend.CUDA_AOT
|
SM_a, SM_b, device_id
|
||||||
)(SM_a, SM_b, device_id)
|
)
|
||||||
|
|
||||||
|
|
||||||
__all__ = ["get_sm_available", "create_greenctx_stream_by_value"]
|
__all__ = ["get_sm_available", "create_greenctx_stream_by_value"]
|
||||||
|
|||||||
@@ -1,16 +1,24 @@
|
|||||||
"""Fixed-path kernel resolution over the :data:`registry`.
|
"""Device-aware fixed-path kernel resolution over the :data:`registry`.
|
||||||
|
|
||||||
There is no priority ranking or heuristic backend selection. Each operator has
|
There is no priority *ranking* or preference heuristic. Resolution of an op's
|
||||||
a fixed call path — its :attr:`KernelSpec.target`:
|
call path is deterministic:
|
||||||
|
|
||||||
- an op with a single registered backend resolves to it directly;
|
- an op with a single registered backend resolves to it directly;
|
||||||
- an op with several registered backends must be resolved by naming the backend
|
- an op with several registered backends is filtered by the detected platform
|
||||||
explicitly (``backend=...``). The extra backends exist only as inventory, and
|
(a hard :class:`~sglang.kernels.spec.CapabilityRequirement` check, not a
|
||||||
are never silently auto-picked.
|
preference). If exactly one backend is usable on this device, it is the fixed
|
||||||
|
call path; if several remain usable, the caller must name the backend
|
||||||
|
explicitly (``backend=...``).
|
||||||
|
|
||||||
|
Because ``KernelBackend`` is now device-agnostic provenance, the *same* backend
|
||||||
|
(e.g. ``AOT``) may be registered for an op on more than one device; the
|
||||||
|
availability filter is what makes ``select_kernel`` pick the right one per
|
||||||
|
platform. Filtering by device is a hard eligibility gate, not the ranked
|
||||||
|
auto-selection that ``BaseFusedOp`` performs.
|
||||||
|
|
||||||
:func:`get_kernel` is the fast path used by the public ``ops.*`` wrappers: it
|
:func:`get_kernel` is the fast path used by the public ``ops.*`` wrappers: it
|
||||||
resolves the spec to its callable and caches the result so repeated calls do
|
resolves the spec to its callable and caches the result so repeated calls do
|
||||||
not re-run resolution or re-import.
|
not re-run resolution or re-import (the platform is constant per process).
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
@@ -19,7 +27,12 @@ from functools import lru_cache
|
|||||||
from typing import Callable, Optional
|
from typing import Callable, Optional
|
||||||
|
|
||||||
from sglang.kernels.registry import registry
|
from sglang.kernels.registry import registry
|
||||||
from sglang.kernels.spec import KernelBackend, KernelSpec
|
from sglang.kernels.spec import KernelBackend, KernelSpec, PlatformInfo
|
||||||
|
|
||||||
|
|
||||||
|
@lru_cache(maxsize=1)
|
||||||
|
def _platform() -> PlatformInfo:
|
||||||
|
return PlatformInfo.detect()
|
||||||
|
|
||||||
|
|
||||||
def select_kernel(op: str, backend: Optional[KernelBackend] = None) -> KernelSpec:
|
def select_kernel(op: str, backend: Optional[KernelBackend] = None) -> KernelSpec:
|
||||||
@@ -30,15 +43,16 @@ def select_kernel(op: str, backend: Optional[KernelBackend] = None) -> KernelSpe
|
|||||||
op:
|
op:
|
||||||
Operator id, ``"<group>.<name>"``.
|
Operator id, ``"<group>.<name>"``.
|
||||||
backend:
|
backend:
|
||||||
Required only when ``op`` has more than one registered backend; selects
|
Required only when ``op`` has more than one backend *usable on the
|
||||||
which one. For single-backend ops it is optional.
|
current device*; selects which one. Otherwise optional.
|
||||||
|
|
||||||
Raises
|
Raises
|
||||||
------
|
------
|
||||||
KeyError
|
KeyError
|
||||||
If ``op`` is unknown, or if ``backend`` is requested but not registered.
|
If ``op`` is unknown, or if ``backend`` is requested but not registered.
|
||||||
ValueError
|
ValueError
|
||||||
If ``op`` has multiple backends and ``backend`` is not given.
|
If ``op`` has multiple device-eligible backends and ``backend`` is not
|
||||||
|
given, or if none are eligible on this platform.
|
||||||
"""
|
"""
|
||||||
specs = registry.get(op)
|
specs = registry.get(op)
|
||||||
if not specs:
|
if not specs:
|
||||||
@@ -53,9 +67,20 @@ def select_kernel(op: str, backend: Optional[KernelBackend] = None) -> KernelSpe
|
|||||||
if len(specs) == 1:
|
if len(specs) == 1:
|
||||||
return specs[0]
|
return specs[0]
|
||||||
|
|
||||||
|
# Multiple backends: hard-filter by device eligibility.
|
||||||
|
platform = _platform()
|
||||||
|
eligible = [s for s in specs if s.is_available(platform)]
|
||||||
|
if len(eligible) == 1:
|
||||||
|
return eligible[0]
|
||||||
|
if not eligible:
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
f"op {op!r} has multiple registered backends "
|
f"op {op!r} has no backend usable on device {platform.device.value!r} "
|
||||||
f"({[s.backend.value for s in specs]}); pass backend=... to choose one"
|
f"(registered: {[s.backend.value for s in specs]})"
|
||||||
|
)
|
||||||
|
raise ValueError(
|
||||||
|
f"op {op!r} has multiple backends usable on device "
|
||||||
|
f"{platform.device.value!r} ({[s.backend.value for s in eligible]}); "
|
||||||
|
f"pass backend=... to choose one"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -8,34 +8,53 @@ box (see RFC #29630, Phase 2).
|
|||||||
|
|
||||||
The concrete callable behind a :class:`KernelSpec` is resolved lazily through
|
The concrete callable behind a :class:`KernelSpec` is resolved lazily through
|
||||||
``KernelSpec.load()``; nothing is imported until a kernel is actually called.
|
``KernelSpec.load()``; nothing is imported until a kernel is actually called.
|
||||||
|
|
||||||
|
Backend vs. device (RFC #29630 follow-up): :class:`KernelBackend` names only the
|
||||||
|
*provenance* of an implementation (how it is built / where it comes from), not
|
||||||
|
the hardware it runs on. Both JIT and AOT sources already build for CUDA *and*
|
||||||
|
ROCm, and a wheel may ship only a per-op subset, so platform support is
|
||||||
|
per-``(op, backend)`` metadata carried by :class:`CapabilityRequirement`, not
|
||||||
|
derivable from the backend name.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import importlib
|
import importlib
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
from typing import Callable, Optional, Tuple
|
from typing import Callable, ClassVar, FrozenSet, Optional, Tuple, Union
|
||||||
|
|
||||||
import msgspec
|
import msgspec
|
||||||
|
|
||||||
|
|
||||||
class KernelBackend(str, Enum):
|
class KernelBackend(str, Enum):
|
||||||
"""Implementation backend for a kernel.
|
"""Provenance of a kernel implementation (how it is built), not its device.
|
||||||
|
|
||||||
Values mirror the backends called out in RFC #29630: JIT CUDA, AOT
|
``JIT`` (``sglang.jit_kernel``, compiles under nvcc *and* hipcc) and ``AOT``
|
||||||
CUDA/C++ (the ``sgl_kernel`` wheel), Triton, CuTe DSL, FlashInfer, DeepGEMM,
|
(the ``sgl_kernel`` wheel, built for CUDA *and* ROCm) are both cross-device;
|
||||||
and the pure-``torch`` fallback path.
|
which devices a given op supports is expressed by its
|
||||||
|
:class:`CapabilityRequirement` list. Platform-specific libraries (e.g.
|
||||||
|
``aiter`` on AMD) are just additional provenance values.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
TORCH = "torch" # pure-torch reference (forward_native)
|
TORCH = "torch" # pure-torch reference (forward_native)
|
||||||
TORCH_COMPILE = "torch_compile" # torch.compile(forward_native)
|
TORCH_COMPILE = "torch_compile" # torch.compile(forward_native)
|
||||||
TRITON = "triton"
|
TRITON = "triton"
|
||||||
CUDA_JIT = "cuda_jit" # sglang.jit_kernel
|
JIT = "jit" # sglang.jit_kernel (nvcc / hipcc)
|
||||||
CUDA_AOT = "cuda_aot" # sgl_kernel wheel
|
AOT = "aot" # sgl_kernel wheel (CUDA / ROCm builds)
|
||||||
CUTE_DSL = "cute_dsl"
|
CUTE_DSL = "cute_dsl"
|
||||||
FLASHINFER = "flashinfer"
|
FLASHINFER = "flashinfer"
|
||||||
DEEPGEMM = "deepgemm"
|
DEEPGEMM = "deepgemm"
|
||||||
# TODO(RFC #29630): backends for other hardware (hip_c / npu / cpu-avx, ...)
|
AITER = "aiter" # AMD aiter library (device=HIP)
|
||||||
|
# TODO(RFC #29630): more provenance as needed (npu / cpu-avx, ...)
|
||||||
|
|
||||||
|
|
||||||
|
class DeviceType(str, Enum):
|
||||||
|
"""Accelerator device family a kernel can run on."""
|
||||||
|
|
||||||
|
CUDA = "cuda"
|
||||||
|
HIP = "hip"
|
||||||
|
CPU = "cpu"
|
||||||
|
# TODO(RFC #29630): NPU / XPU / ... as backends land.
|
||||||
|
|
||||||
|
|
||||||
class PlatformInfo(msgspec.Struct, frozen=True):
|
class PlatformInfo(msgspec.Struct, frozen=True):
|
||||||
@@ -49,6 +68,13 @@ class PlatformInfo(msgspec.Struct, frozen=True):
|
|||||||
cuda_arch_major: Optional[int] = None
|
cuda_arch_major: Optional[int] = None
|
||||||
cuda_arch_minor: Optional[int] = None
|
cuda_arch_minor: Optional[int] = None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def device(self) -> DeviceType:
|
||||||
|
try:
|
||||||
|
return DeviceType(self.device_type)
|
||||||
|
except (ValueError, TypeError):
|
||||||
|
return DeviceType.CPU
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_cuda(self) -> bool:
|
def is_cuda(self) -> bool:
|
||||||
return self.device_type == "cuda"
|
return self.device_type == "cuda"
|
||||||
@@ -85,23 +111,45 @@ class PlatformInfo(msgspec.Struct, frozen=True):
|
|||||||
|
|
||||||
|
|
||||||
class CapabilityRequirement(msgspec.Struct, frozen=True):
|
class CapabilityRequirement(msgspec.Struct, frozen=True):
|
||||||
"""Coarse hardware requirement used to filter out unusable backends.
|
"""One device (plus an optional CUDA-arch window) a backend can run on.
|
||||||
|
|
||||||
``min_cuda_arch`` / ``max_cuda_arch`` are ``(major, minor)`` tuples, e.g.
|
A :class:`KernelSpec` / :class:`~sglang.kernels.fused_op.BaseFusedOp` backend
|
||||||
``(9, 0)`` for SM90. They only apply when the kernel requires CUDA.
|
carries a *set* of these with **OR** semantics — any matching entry makes the
|
||||||
|
backend eligible, and an empty set means unrestricted (runs anywhere). A set
|
||||||
|
(not a tuple) because order and duplicates are meaningless here: ``{CUDA,
|
||||||
|
HIP}`` and ``{HIP, CUDA}`` describe the same thing. This replaces the old
|
||||||
|
``requires_cuda`` / ``requires_hip`` booleans (whose AND semantics could not
|
||||||
|
express "CUDA or HIP"); arch bounds now attach to the device they describe
|
||||||
|
(``min_cuda_arch`` / ``max_cuda_arch`` apply only when ``device == CUDA``).
|
||||||
|
|
||||||
|
The device-only cases are so common that they are exposed as class constants
|
||||||
|
(``CapabilityRequirement.CUDA`` / ``.HIP``); use :meth:`cuda` for an
|
||||||
|
arch-bounded CUDA requirement (e.g. ``CapabilityRequirement.cuda(
|
||||||
|
min_sm=(10, 0))`` for SM100+).
|
||||||
"""
|
"""
|
||||||
|
|
||||||
requires_cuda: bool = False
|
device: DeviceType
|
||||||
requires_hip: bool = False
|
|
||||||
min_cuda_arch: Optional[Tuple[int, int]] = None
|
min_cuda_arch: Optional[Tuple[int, int]] = None
|
||||||
max_cuda_arch: Optional[Tuple[int, int]] = None
|
max_cuda_arch: Optional[Tuple[int, int]] = None
|
||||||
|
|
||||||
|
# Common device-only shortcuts, assigned after the class body (they are
|
||||||
|
# instances of the class itself). ClassVar keeps them out of msgspec fields.
|
||||||
|
CUDA: ClassVar[CapabilityRequirement]
|
||||||
|
HIP: ClassVar[CapabilityRequirement]
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def cuda(
|
||||||
|
cls,
|
||||||
|
min_sm: Optional[Tuple[int, int]] = None,
|
||||||
|
max_sm: Optional[Tuple[int, int]] = None,
|
||||||
|
) -> CapabilityRequirement:
|
||||||
|
"""A CUDA requirement bounded to an SM-arch window (inclusive)."""
|
||||||
|
return cls(device=DeviceType.CUDA, min_cuda_arch=min_sm, max_cuda_arch=max_sm)
|
||||||
|
|
||||||
def is_satisfied_by(self, platform: PlatformInfo) -> bool:
|
def is_satisfied_by(self, platform: PlatformInfo) -> bool:
|
||||||
if self.requires_hip and not platform.is_hip:
|
if self.device != platform.device:
|
||||||
return False
|
return False
|
||||||
if self.requires_cuda and not platform.is_cuda:
|
if self.device == DeviceType.CUDA and platform.cuda_arch_major is not None:
|
||||||
return False
|
|
||||||
if platform.is_cuda and platform.cuda_arch_major is not None:
|
|
||||||
arch = (platform.cuda_arch_major, platform.cuda_arch_minor or 0)
|
arch = (platform.cuda_arch_major, platform.cuda_arch_minor or 0)
|
||||||
if self.min_cuda_arch is not None and arch < self.min_cuda_arch:
|
if self.min_cuda_arch is not None and arch < self.min_cuda_arch:
|
||||||
return False
|
return False
|
||||||
@@ -110,6 +158,28 @@ class CapabilityRequirement(msgspec.Struct, frozen=True):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
CapabilityRequirement.CUDA = CapabilityRequirement(device=DeviceType.CUDA)
|
||||||
|
CapabilityRequirement.HIP = CapabilityRequirement(device=DeviceType.HIP)
|
||||||
|
|
||||||
|
|
||||||
|
def capabilities_satisfied(
|
||||||
|
capabilities: Union[
|
||||||
|
FrozenSet[CapabilityRequirement],
|
||||||
|
Tuple[CapabilityRequirement, ...],
|
||||||
|
CapabilityRequirement,
|
||||||
|
],
|
||||||
|
platform: PlatformInfo,
|
||||||
|
) -> bool:
|
||||||
|
"""OR over ``capabilities`` (empty = unrestricted).
|
||||||
|
|
||||||
|
Accepts a set/tuple of requirements, or tolerates a single
|
||||||
|
:class:`CapabilityRequirement` (the pre-decouple API used one) by wrapping it.
|
||||||
|
"""
|
||||||
|
if isinstance(capabilities, CapabilityRequirement):
|
||||||
|
capabilities = (capabilities,)
|
||||||
|
return (not capabilities) or any(c.is_satisfied_by(platform) for c in capabilities)
|
||||||
|
|
||||||
|
|
||||||
class FormatSignature(msgspec.Struct, frozen=True):
|
class FormatSignature(msgspec.Struct, frozen=True):
|
||||||
"""A light description of a kernel's data contract.
|
"""A light description of a kernel's data contract.
|
||||||
|
|
||||||
@@ -131,15 +201,17 @@ class KernelSpec(msgspec.Struct, frozen=True):
|
|||||||
Fully-qualified operator id, ``"<group>.<name>"`` (e.g.
|
Fully-qualified operator id, ``"<group>.<name>"`` (e.g.
|
||||||
``"layernorm.rmsnorm"``). This is the public lookup key.
|
``"layernorm.rmsnorm"``). This is the public lookup key.
|
||||||
backend:
|
backend:
|
||||||
Which :class:`KernelBackend` provides this implementation.
|
Which :class:`KernelBackend` (provenance) provides this implementation.
|
||||||
target:
|
target:
|
||||||
Import path of the callable in ``"module:attr"`` form, resolved lazily
|
Import path of the callable in ``"module:attr"`` form, resolved lazily
|
||||||
by :meth:`load` (e.g. ``"sgl_kernel:rmsnorm"``). ``attr`` may be a
|
by :meth:`load` (e.g. ``"sgl_kernel:rmsnorm"``). ``attr`` may be a
|
||||||
dotted path into a module-level object, e.g.
|
dotted path into a module-level object, e.g.
|
||||||
``"sglang.kernels.ops.layernorm:_RMSNORM.forward_cuda_aot"`` for a
|
``"sglang.kernels.ops.layernorm:_RMSNORM.forward_aot"`` for a bound
|
||||||
bound :class:`~sglang.kernels.fused_op.BaseFusedOp` backend method.
|
:class:`~sglang.kernels.fused_op.BaseFusedOp` backend method.
|
||||||
capability:
|
capabilities:
|
||||||
Hardware requirement used by the selector to skip unusable backends.
|
Set of :class:`CapabilityRequirement` (OR semantics; empty = runs on
|
||||||
|
any device) used by the selector to skip backends unusable on the
|
||||||
|
detected platform.
|
||||||
format_signature:
|
format_signature:
|
||||||
Optional data-contract description for inventory/documentation.
|
Optional data-contract description for inventory/documentation.
|
||||||
description:
|
description:
|
||||||
@@ -149,9 +221,7 @@ class KernelSpec(msgspec.Struct, frozen=True):
|
|||||||
op: str
|
op: str
|
||||||
backend: KernelBackend
|
backend: KernelBackend
|
||||||
target: str
|
target: str
|
||||||
capability: CapabilityRequirement = msgspec.field(
|
capabilities: FrozenSet[CapabilityRequirement] = frozenset()
|
||||||
default_factory=CapabilityRequirement
|
|
||||||
)
|
|
||||||
format_signature: FormatSignature = msgspec.field(default_factory=FormatSignature)
|
format_signature: FormatSignature = msgspec.field(default_factory=FormatSignature)
|
||||||
description: str = ""
|
description: str = ""
|
||||||
|
|
||||||
@@ -165,7 +235,7 @@ class KernelSpec(msgspec.Struct, frozen=True):
|
|||||||
|
|
||||||
def is_available(self, platform: PlatformInfo) -> bool:
|
def is_available(self, platform: PlatformInfo) -> bool:
|
||||||
"""Whether this backend can run on ``platform`` (metadata-only check)."""
|
"""Whether this backend can run on ``platform`` (metadata-only check)."""
|
||||||
return self.capability.is_satisfied_by(platform)
|
return capabilities_satisfied(self.capabilities, platform)
|
||||||
|
|
||||||
def load(self) -> Callable:
|
def load(self) -> Callable:
|
||||||
"""Import and return the backing callable.
|
"""Import and return the backing callable.
|
||||||
|
|||||||
@@ -701,7 +701,7 @@ class Envs:
|
|||||||
|
|
||||||
# Kernels
|
# Kernels
|
||||||
# Force every sglang.kernels BaseFusedOp onto one backend (a KernelBackend
|
# Force every sglang.kernels BaseFusedOp onto one backend (a KernelBackend
|
||||||
# value, e.g. "torch" / "torch_compile" / "triton" / "cuda_aot"); unset =
|
# value, e.g. "torch" / "torch_compile" / "triton" / "aot"); unset =
|
||||||
# auto-select by priority. "torch" flips all fused ops to their pure-torch
|
# auto-select by priority. "torch" flips all fused ops to their pure-torch
|
||||||
# reference implementations for numerical-bug bisection.
|
# reference implementations for numerical-bug bisection.
|
||||||
SGLANG_FORCE_FUSED_OP_BACKEND = EnvStr(None)
|
SGLANG_FORCE_FUSED_OP_BACKEND = EnvStr(None)
|
||||||
|
|||||||
@@ -43,13 +43,13 @@ class _CudaOnlyToyOp(BaseFusedOp):
|
|||||||
"""Toy op whose optimized backend requires CUDA (never eligible on CPU)."""
|
"""Toy op whose optimized backend requires CUDA (never eligible on CPU)."""
|
||||||
|
|
||||||
op = "test.toy_cuda_only"
|
op = "test.toy_cuda_only"
|
||||||
priority = (KernelBackend.CUDA_AOT, KernelBackend.TORCH)
|
priority = (KernelBackend.AOT, KernelBackend.TORCH)
|
||||||
capabilities = {KernelBackend.CUDA_AOT: CapabilityRequirement(requires_cuda=True)}
|
capabilities = {KernelBackend.AOT: {CapabilityRequirement.CUDA}}
|
||||||
|
|
||||||
def forward_native(self, a):
|
def forward_native(self, a):
|
||||||
return a * 2
|
return a * 2
|
||||||
|
|
||||||
def forward_cuda_aot(self, a):
|
def forward_aot(self, a):
|
||||||
raise AssertionError("must not be selected on a CPU-only box")
|
raise AssertionError("must not be selected on a CPU-only box")
|
||||||
|
|
||||||
|
|
||||||
@@ -117,7 +117,7 @@ class TestBaseFusedOp(unittest.TestCase):
|
|||||||
op.forward(
|
op.forward(
|
||||||
torch.tensor([1.0]),
|
torch.tensor([1.0]),
|
||||||
torch.tensor([2.0]),
|
torch.tensor([2.0]),
|
||||||
backend=KernelBackend.CUDA_AOT,
|
backend=KernelBackend.AOT,
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_torch_compile_backend(self):
|
def test_torch_compile_backend(self):
|
||||||
@@ -150,8 +150,8 @@ class TestBaseFusedOp(unittest.TestCase):
|
|||||||
{
|
{
|
||||||
KernelBackend.TORCH,
|
KernelBackend.TORCH,
|
||||||
KernelBackend.TORCH_COMPILE,
|
KernelBackend.TORCH_COMPILE,
|
||||||
KernelBackend.CUDA_JIT,
|
KernelBackend.JIT,
|
||||||
KernelBackend.CUDA_AOT,
|
KernelBackend.AOT,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
# Dotted targets resolve to the bound backend methods.
|
# Dotted targets resolve to the bound backend methods.
|
||||||
|
|||||||
@@ -20,50 +20,50 @@ register_cpu_ci(est_time=10, suite="base-a-test-cpu")
|
|||||||
EXPECTED_OPS = {
|
EXPECTED_OPS = {
|
||||||
# BaseFusedOp-backed ops: native + torch_compile always available,
|
# BaseFusedOp-backed ops: native + torch_compile always available,
|
||||||
# plus the overridden CUDA backends.
|
# plus the overridden CUDA backends.
|
||||||
"activation.silu_and_mul": {"cuda_aot", "cuda_jit", "torch", "torch_compile"},
|
"activation.silu_and_mul": {"aot", "jit", "aiter", "torch", "torch_compile"},
|
||||||
"activation.gelu_and_mul": {"cuda_aot", "cuda_jit", "torch", "torch_compile"},
|
"activation.gelu_and_mul": {"aot", "jit", "torch", "torch_compile"},
|
||||||
"activation.gelu_tanh_and_mul": {
|
"activation.gelu_tanh_and_mul": {
|
||||||
"cuda_aot",
|
"aot",
|
||||||
"cuda_jit",
|
"jit",
|
||||||
"torch",
|
"torch",
|
||||||
"torch_compile",
|
"torch_compile",
|
||||||
},
|
},
|
||||||
"layernorm.rmsnorm": {"cuda_aot", "cuda_jit", "torch", "torch_compile"},
|
"layernorm.rmsnorm": {"aot", "jit", "torch", "torch_compile"},
|
||||||
"layernorm.fused_add_rmsnorm": {
|
"layernorm.fused_add_rmsnorm": {
|
||||||
"cuda_aot",
|
"aot",
|
||||||
"cuda_jit",
|
"jit",
|
||||||
"torch",
|
"torch",
|
||||||
"torch_compile",
|
"torch_compile",
|
||||||
},
|
},
|
||||||
"layernorm.gemma_rmsnorm": {"cuda_aot", "torch", "torch_compile"},
|
"layernorm.gemma_rmsnorm": {"aot", "torch", "torch_compile"},
|
||||||
"layernorm.gemma_fused_add_rmsnorm": {"cuda_aot", "torch", "torch_compile"},
|
"layernorm.gemma_fused_add_rmsnorm": {"aot", "torch", "torch_compile"},
|
||||||
# curated dual/single-backend wrapper ops
|
# curated dual/single-backend wrapper ops
|
||||||
"gemm.fp8_scaled_mm": {"cuda_aot"},
|
"gemm.fp8_scaled_mm": {"aot"},
|
||||||
"gemm.dsv3_fused_a_gemm": {"cuda_aot", "cuda_jit"},
|
"gemm.dsv3_fused_a_gemm": {"aot", "jit"},
|
||||||
"gemm.dsv3_router_gemm": {"cuda_jit"},
|
"gemm.dsv3_router_gemm": {"jit"},
|
||||||
"kvcache.reshape_and_cache_flash": {"triton"},
|
"kvcache.reshape_and_cache_flash": {"triton"},
|
||||||
"moe.moe_align_block_size": {"cuda_aot", "cuda_jit"},
|
"moe.moe_align_block_size": {"aot", "jit"},
|
||||||
"moe.topk_softmax": {"cuda_aot"},
|
"moe.topk_softmax": {"aot"},
|
||||||
"quantization.sgl_per_token_quant_fp8": {"cuda_aot"},
|
"quantization.sgl_per_token_quant_fp8": {"aot"},
|
||||||
# migrated from srt/layers/quantization (Phase 2.5)
|
# migrated from srt/layers/quantization (Phase 2.5)
|
||||||
"quantization.w8a8_block_fp8_matmul": {"triton"},
|
"quantization.w8a8_block_fp8_matmul": {"triton"},
|
||||||
"quantization.per_token_quant_int8": {"triton"},
|
"quantization.per_token_quant_int8": {"triton"},
|
||||||
"quantization.awq_dequantize_triton": {"triton"},
|
"quantization.awq_dequantize_triton": {"triton"},
|
||||||
"quantization.nvfp4_gemm_swiglu_nvfp4_quant": {"cute_dsl"},
|
"quantization.nvfp4_gemm_swiglu_nvfp4_quant": {"cute_dsl"},
|
||||||
"moe.pack_topk_ids": {"triton"},
|
"moe.pack_topk_ids": {"triton"},
|
||||||
"quantization.sgl_per_token_group_quant_8bit": {"cuda_aot", "cuda_jit"},
|
"quantization.sgl_per_token_group_quant_8bit": {"aot", "jit"},
|
||||||
"quantization.sgl_per_token_group_quant_fp8": {"cuda_aot"},
|
"quantization.sgl_per_token_group_quant_fp8": {"aot"},
|
||||||
"quantization.sgl_per_token_group_quant_int8": {"cuda_aot"},
|
"quantization.sgl_per_token_group_quant_int8": {"aot"},
|
||||||
# deferred-group wrappers, now populated
|
# deferred-group wrappers, now populated
|
||||||
"sampling.top_k_renorm_probs": {"cuda_aot"},
|
"sampling.top_k_renorm_probs": {"aot"},
|
||||||
"sampling.top_p_renorm_probs": {"cuda_aot"},
|
"sampling.top_p_renorm_probs": {"aot"},
|
||||||
"spatial.get_sm_available": {"cuda_aot"},
|
"spatial.get_sm_available": {"aot"},
|
||||||
"spatial.create_greenctx_stream_by_value": {"cuda_aot"},
|
"spatial.create_greenctx_stream_by_value": {"aot"},
|
||||||
"mamba.causal_conv1d_fwd": {"cuda_aot"},
|
"mamba.causal_conv1d_fwd": {"aot"},
|
||||||
"mamba.causal_conv1d_update": {"cuda_aot"},
|
"mamba.causal_conv1d_update": {"aot"},
|
||||||
"diffusion.apply_group_norm_silu": {"cuda_jit"},
|
"diffusion.apply_group_norm_silu": {"jit"},
|
||||||
"diffusion.residual_gate_add": {"cuda_jit"},
|
"diffusion.residual_gate_add": {"jit"},
|
||||||
"diffusion.fused_inplace_qknorm_rope": {"cuda_jit"},
|
"diffusion.fused_inplace_qknorm_rope": {"jit"},
|
||||||
# representative migrated Triton kernels (inventory)
|
# representative migrated Triton kernels (inventory)
|
||||||
"grammar.apply_token_bitmask_inplace_triton": {"triton"},
|
"grammar.apply_token_bitmask_inplace_triton": {"triton"},
|
||||||
"memory.alloc_extend_kernel": {"triton"},
|
"memory.alloc_extend_kernel": {"triton"},
|
||||||
@@ -197,19 +197,64 @@ class TestKernelsNamespace(unittest.TestCase):
|
|||||||
self.assertEqual(spec.backend.value, next(iter(backends)), op)
|
self.assertEqual(spec.backend.value, next(iter(backends)), op)
|
||||||
|
|
||||||
def test_multi_backend_op_requires_explicit_backend(self):
|
def test_multi_backend_op_requires_explicit_backend(self):
|
||||||
# No hidden ranking: a multi-backend op must be resolved explicitly.
|
# Device is a HARD eligibility filter, not a preference ranking: when
|
||||||
multi = [op for op, b in EXPECTED_OPS.items() if len(b) > 1]
|
# more than one backend is usable on the current device, selection must
|
||||||
self.assertTrue(multi) # sanity: we do have multi-backend ops
|
# be explicit (no hidden auto-ranking). Force a CUDA platform so the
|
||||||
for op in multi:
|
# result is deterministic regardless of the test host.
|
||||||
|
import sglang.kernels.selector as sel
|
||||||
|
|
||||||
|
saved = sel._platform
|
||||||
|
try:
|
||||||
|
sel._platform = lambda: self.K.PlatformInfo(
|
||||||
|
device_type="cuda", cuda_arch_major=9, cuda_arch_minor=0
|
||||||
|
)
|
||||||
|
# rmsnorm exposes torch/torch_compile/jit/aot, all eligible on CUDA.
|
||||||
with self.assertRaises(ValueError):
|
with self.assertRaises(ValueError):
|
||||||
self.K.select_kernel(op)
|
self.K.select_kernel("layernorm.rmsnorm")
|
||||||
|
# An explicit backend is always the fixed call path.
|
||||||
|
spec = self.K.select_kernel(
|
||||||
|
"layernorm.rmsnorm", backend=self.K.KernelBackend.JIT
|
||||||
|
)
|
||||||
|
self.assertEqual(spec.backend, self.K.KernelBackend.JIT)
|
||||||
|
finally:
|
||||||
|
sel._platform = saved
|
||||||
|
|
||||||
|
def test_decoupled_backend_device_selection(self):
|
||||||
|
# Proves the decoupled backend/device model against production reality:
|
||||||
|
# - AOT (sgl_kernel) spans CUDA *and* HIP (OR-semantics capability);
|
||||||
|
# - JIT is CUDA-only; AITER is an opt-in HIP-only path on silu_and_mul;
|
||||||
|
# - gelu_and_mul has no AITER kernel (per-(op, backend) subset).
|
||||||
|
# Auto-selection matches production defaults: JIT on CUDA, AOT on HIP.
|
||||||
|
import sglang.kernels.fused_op as fo
|
||||||
|
from sglang.kernels.ops.activation import _GELU_AND_MUL, _SILU_AND_MUL
|
||||||
|
|
||||||
|
B = self.K.KernelBackend
|
||||||
|
hip = self.K.PlatformInfo(device_type="hip")
|
||||||
|
cuda = self.K.PlatformInfo(device_type="cuda", cuda_arch_major=9)
|
||||||
|
saved = fo._platform
|
||||||
|
try:
|
||||||
|
fo._platform = lambda: hip
|
||||||
|
# silu implements AITER (a HIP kernel); gelu does not (per-op subset).
|
||||||
|
self.assertIn(B.AITER, _SILU_AND_MUL.available_backends())
|
||||||
|
self.assertNotIn(B.AITER, _GELU_AND_MUL.available_backends())
|
||||||
|
self.assertTrue(_SILU_AND_MUL.backend_eligible(B.AOT)) # (cuda, hip)
|
||||||
|
self.assertTrue(_SILU_AND_MUL.backend_eligible(B.AITER)) # hip-only
|
||||||
|
self.assertFalse(_SILU_AND_MUL.backend_eligible(B.JIT)) # cuda-only
|
||||||
|
# HIP default = AOT (production default); AITER is opt-in below it.
|
||||||
|
self.assertEqual(_SILU_AND_MUL._resolve_backend(), B.AOT)
|
||||||
|
# gelu has no AITER but AOT spans HIP -> resolves to AOT, not torch.
|
||||||
|
self.assertEqual(_GELU_AND_MUL._resolve_backend(), B.AOT)
|
||||||
|
fo._platform = lambda: cuda
|
||||||
|
self.assertEqual(_SILU_AND_MUL._resolve_backend(), B.JIT) # CUDA default
|
||||||
|
finally:
|
||||||
|
fo._platform = saved
|
||||||
|
|
||||||
def test_selector_explicit_backend(self):
|
def test_selector_explicit_backend(self):
|
||||||
spec = self.K.select_kernel(
|
spec = self.K.select_kernel(
|
||||||
"layernorm.rmsnorm", backend=self.K.KernelBackend.CUDA_JIT
|
"layernorm.rmsnorm", backend=self.K.KernelBackend.JIT
|
||||||
)
|
)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
spec.target, "sglang.kernels.ops.layernorm:_RMSNORM.forward_cuda_jit"
|
spec.target, "sglang.kernels.ops.layernorm:_RMSNORM.forward_jit"
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_selector_unknown_op_raises(self):
|
def test_selector_unknown_op_raises(self):
|
||||||
@@ -222,23 +267,50 @@ class TestKernelsNamespace(unittest.TestCase):
|
|||||||
|
|
||||||
def test_capability_requirement_logic(self):
|
def test_capability_requirement_logic(self):
|
||||||
cap = self.K.CapabilityRequirement
|
cap = self.K.CapabilityRequirement
|
||||||
|
dev = self.K.DeviceType
|
||||||
plat = self.K.PlatformInfo
|
plat = self.K.PlatformInfo
|
||||||
cpu = plat(device_type="cpu")
|
cpu = plat(device_type="cpu")
|
||||||
sm90 = plat(device_type="cuda", cuda_arch_major=9, cuda_arch_minor=0)
|
sm90 = plat(device_type="cuda", cuda_arch_major=9, cuda_arch_minor=0)
|
||||||
sm100 = plat(device_type="cuda", cuda_arch_major=10, cuda_arch_minor=0)
|
sm100 = plat(device_type="cuda", cuda_arch_major=10, cuda_arch_minor=0)
|
||||||
|
hip = plat(device_type="hip")
|
||||||
|
|
||||||
self.assertFalse(cap(requires_cuda=True).is_satisfied_by(cpu))
|
self.assertFalse(cap(device=dev.CUDA).is_satisfied_by(cpu))
|
||||||
self.assertTrue(cap(requires_cuda=True).is_satisfied_by(sm90))
|
self.assertTrue(cap(device=dev.CUDA).is_satisfied_by(sm90))
|
||||||
|
self.assertFalse(cap(device=dev.CUDA).is_satisfied_by(hip))
|
||||||
|
self.assertTrue(cap(device=dev.HIP).is_satisfied_by(hip))
|
||||||
self.assertFalse(
|
self.assertFalse(
|
||||||
cap(requires_cuda=True, min_cuda_arch=(10, 0)).is_satisfied_by(sm90)
|
cap(device=dev.CUDA, min_cuda_arch=(10, 0)).is_satisfied_by(sm90)
|
||||||
)
|
)
|
||||||
self.assertTrue(
|
self.assertTrue(
|
||||||
cap(requires_cuda=True, min_cuda_arch=(10, 0)).is_satisfied_by(sm100)
|
cap(device=dev.CUDA, min_cuda_arch=(10, 0)).is_satisfied_by(sm100)
|
||||||
)
|
)
|
||||||
self.assertFalse(
|
self.assertFalse(
|
||||||
cap(requires_cuda=True, max_cuda_arch=(9, 0)).is_satisfied_by(sm100)
|
cap(device=dev.CUDA, max_cuda_arch=(9, 0)).is_satisfied_by(sm100)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# OR semantics: a {cuda, hip} set is satisfied by either device.
|
||||||
|
cuda_or_hip = {cap.CUDA, cap.HIP}
|
||||||
|
self.assertTrue(self.K.capabilities_satisfied(cuda_or_hip, sm90))
|
||||||
|
self.assertTrue(self.K.capabilities_satisfied(cuda_or_hip, hip))
|
||||||
|
self.assertFalse(self.K.capabilities_satisfied(cuda_or_hip, cpu))
|
||||||
|
self.assertTrue(self.K.capabilities_satisfied((), cpu)) # empty = unrestricted
|
||||||
|
# single requirement is tolerated (pre-decouple API used one).
|
||||||
|
self.assertTrue(self.K.capabilities_satisfied(cap.CUDA, sm90))
|
||||||
|
|
||||||
|
# Class-constant shortcuts equal their explicit form; sets are unordered
|
||||||
|
# and dedup, so {CUDA, HIP} == {HIP, CUDA}.
|
||||||
|
self.assertEqual(cap.CUDA, cap(device=dev.CUDA))
|
||||||
|
self.assertEqual(cap.HIP, cap(device=dev.HIP))
|
||||||
|
self.assertEqual({cap.CUDA, cap.HIP}, {cap.HIP, cap.CUDA})
|
||||||
|
self.assertEqual(len({cap.CUDA, cap(device=dev.CUDA)}), 1)
|
||||||
|
# cuda(min_sm=...) factory: an SM100+ CUDA requirement.
|
||||||
|
self.assertEqual(
|
||||||
|
cap.cuda(min_sm=(10, 0)),
|
||||||
|
cap(device=dev.CUDA, min_cuda_arch=(10, 0)),
|
||||||
|
)
|
||||||
|
self.assertTrue(cap.cuda(min_sm=(10, 0)).is_satisfied_by(sm100))
|
||||||
|
self.assertFalse(cap.cuda(min_sm=(10, 0)).is_satisfied_by(sm90))
|
||||||
|
|
||||||
def test_platform_detect_does_not_raise(self):
|
def test_platform_detect_does_not_raise(self):
|
||||||
plat = self.K.PlatformInfo.detect()
|
plat = self.K.PlatformInfo.detect()
|
||||||
self.assertIn(plat.device_type, ("cpu", "cuda", "hip"))
|
self.assertIn(plat.device_type, ("cpu", "cuda", "hip"))
|
||||||
|
|||||||
Reference in New Issue
Block a user