Files
wehub-resource-sync 94057c3d3e
PR Test (NPU) / check-changes (push) Has been cancelled
PR Test (NPU) / pr-gate (push) Has been cancelled
PR Test (NPU) / set-image-config (push) Has been cancelled
PR Test (NPU) / stage-b-test-1-npu-a2 (0) (push) Has been cancelled
PR Test (NPU) / stage-b-test-1-npu-a2 (1) (push) Has been cancelled
PR Test (NPU) / stage-b-test-2-npu-a2 (0) (push) Has been cancelled
PR Test (NPU) / stage-b-test-2-npu-a2 (1) (push) Has been cancelled
PR Test (NPU) / stage-b-test-4-npu-a3 (push) Has been cancelled
PR Test (NPU) / stage-b-test-16-npu-a3 (push) Has been cancelled
PR Test (NPU) / multimodal-gen-test-1-npu-a3 (push) Has been cancelled
PR Test (NPU) / multimodal-gen-test-2-npu-a3 (push) Has been cancelled
PR Test (Arm64) / pr-gate (push) Has been cancelled
PR Test (Arm64) / check-changes (push) Has been cancelled
PR Test (Arm64) / build-test (push) Has been cancelled
PR Test (sgl-router) / gate (push) Has been cancelled
PR Test (sgl-router) / tier-1 — lint (push) Has been cancelled
PR Test (sgl-router) / tier-2 — build + test (push) Has been cancelled
PR Test (sgl-router) / tier-3 — docker (placeholder) (push) Has been cancelled
PR Test (sgl-router) / tier-3 — k8s integration (push) Has been cancelled
PR Test (sgl-router) / tier-3 — e2e (push) Has been cancelled
PR Test (sgl-router) / finish (push) Has been cancelled
PR Test (NPU) / single-node-poc (map[name:qwen3_6_27b_w8a8_1p_in64k_out1k_50ms runner:linux-aarch64-a3-2 test_case:test/registered/ascend/performance/qwen3_6_27b/test_npu_qwen3_6_27b_w8a8_1p_in64k_out1k_50ms.py test_type:perf]) (push) Has been cancelled
PR Test (NPU) / pr-test-npu-finish (push) Has been cancelled
PR Test (Xeon) / pr-gate (push) Has been cancelled
PR Test (Xeon) / check-changes (push) Has been cancelled
PR Test (Xeon) / build-test (, xeon-gnr, base-b-test-cpu) (push) Has been cancelled
PR Test (XPU) / check-changes (push) Has been cancelled
PR Test (XPU) / pr-gate (push) Has been cancelled
PR Test (XPU) / stage-a-test-1-gpu-xpu (push) Has been cancelled
PR Test (XPU) / wait-for-stage-a (push) Has been cancelled
PR Test (XPU) / stage-b-test-1-gpu-xpu (push) Has been cancelled
PR Test (XPU) / finish (push) Has been cancelled
CI Model Inventory / build-inventory (push) Has been cancelled
Lint / lint (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark Compilation Check (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark - Manual Policy (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark - Request Processing (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark Summary (push) Has been cancelled
PR Test (SMG) / build-wheel (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on windows (x86_64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on macos (x86_64 - auto) (push) Has been cancelled
PR Test (SMG) / python-unit-tests (push) Has been cancelled
PR Test (SMG) / unit-tests (push) Has been cancelled
PR Test (SMG) / benchmarks (push) Has been cancelled
PR Test (SMG) / chat-completions (push) Has been cancelled
PR Test (SMG) / chat-completions-4gpu (push) Has been cancelled
PR Test (SMG) / e2e (push) Has been cancelled
PR Test (SMG) / docker-build-test (push) Has been cancelled
PR Test (SMG) / k8s-integration (push) Has been cancelled
PR Test (SMG) / finish (push) Has been cancelled
PR Test (SMG) / summarize-benchmarks (push) Has been cancelled
Release SGLang Model Gateway Docker Image / publish (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on macos (aarch64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (aarch64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (x86_64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (aarch64 - musllinux_1_1) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (x86_64 - musllinux_1_1) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / Build SDist (push) Has been cancelled
Release SGLang Model Gateway to PyPI / Upload to PyPI (push) Has been cancelled
Release SGLang Kernels / build-cu129-matrix (aarch64, 12.9, 3.10, arm-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / build-cu129-matrix (x86_64, 12.9, 3.10, x64-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / release-cu129 (push) Has been cancelled
Release SGLang Kernels / build-cu130-matrix (aarch64, 13.0, 3.10, arm-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / build-cu130-matrix (x86_64, 13.0, 3.10, x64-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / release-cu130 (push) Has been cancelled
Release SGLang Kernels / build-rocm-matrix (3.10, 700) (push) Has been cancelled
Release SGLang Kernels / build-rocm-matrix (3.10, 720) (push) Has been cancelled
Release SGLang Kernels / release-rocm700 (push) Has been cancelled
Release SGLang Kernels / release-rocm720 (push) Has been cancelled
Release SGLang Kernels / build-musa43 (43, 3.10) (push) Has been cancelled
Release SGLang Kernels / release-musa43 (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:38:16 +08:00

338 lines
12 KiB
Python

from __future__ import annotations
import inspect
from typing import Any, Callable, List, Optional, TypeVar, Union, overload
import torch
import torch.library
from sglang.kernel_api_logging import debug_torch_op
F = TypeVar("F", bound=Callable)
@overload
def register_custom_op(
fn: F,
*,
op_name: Optional[str] = None,
mutates_args: Optional[List[str]] = None,
out_shape: Optional[Union[int, str]] = None,
eager: bool = True,
) -> F: ...
@overload
def register_custom_op(
fn: F,
*,
op_name: Optional[str] = None,
mutates_args: Optional[List[str]] = None,
fake_impl: Optional[Callable],
eager: bool = True,
) -> F: ...
@overload
def register_custom_op(
*,
op_name: Optional[str] = None,
mutates_args: Optional[List[str]] = None,
out_shape: Optional[Union[int, str]] = None,
eager: bool = True,
) -> Callable[[F], F]: ...
@overload
def register_custom_op(
*,
op_name: Optional[str] = None,
mutates_args: Optional[List[str]] = None,
fake_impl: Optional[Callable],
eager: bool = True,
) -> Callable[[F], F]: ...
# Real implementation
def register_custom_op(
fn: Optional[Callable] = None,
*,
op_name: Optional[str] = None,
mutates_args: Optional[List[str]] = None,
eager: bool = True,
**extra_kwargs,
) -> Any:
"""
A decorator to register a custom operator.
Example usage:
```python
# inplace operator, out_shape is None by default
@register_custom_op(mutates_args=["x"])
def add_1_(x: torch.Tensor) -> None:
x.add_(1)
# operator with output, out_shape indicates the position of output
@register_custom_op(mutates_args=["x"], out_shape=0)
def add(x: torch.Tensor, y: torch.Tensor) -> torch.Tensor:
return x.add_(y)
```
:param fn: The function to be registered as a custom operator.
If None, return a decorator.
:type fn: Callable
:param op_name: The name of the operator. If None, use the function name
:type op_name: Optional[str]
:param mutates_args: A list of argument names that are mutated in-place.
:type mutates_args: List[str]
:param out_shape: The position (int for positional, str for keyword) of the output-shape tensor.
It is used to generate a fake implementation for torch.compile compatibility.
If the operator is inplace and has no output, set to None.
:type out_shape: Optional[List[Union[int, str]]]
:param fake_impl: A fake implementation for the operator.
Only one of `out_shape` or `fake_impl` should be provided.
:type fake_impl: Optional[Callable]
:param eager: Whether to register the operator eagerly.
If False, the registration will be deferred until the first call.
If you met any issue with torch.compile, try to set eager=True.
Currently, to avoid misuse, we set eager=True by default.
:type eager: bool
:return: The registered JIT custom operator, or a decorator.
NOTE: the real register will occur at the first call of the function.
:rtype: Callable
"""
extra_kwarg_keys = set(extra_kwargs.keys())
expected_kwarg_keys = set({"out_shape", "fake_impl"})
assert (
expected_kwarg_keys >= extra_kwarg_keys
), f"Unexpected extra kwargs: {extra_kwarg_keys - expected_kwarg_keys}"
has_out_shape = "out_shape" in extra_kwargs
has_fake_impl = "fake_impl" in extra_kwargs
assert not (
has_out_shape and has_fake_impl
), "Only one of `out_shape` or `fake_impl` should be provided."
# Assume inplace if neither out_shape nor fake_impl is provided
if not (has_out_shape or has_fake_impl):
extra_kwargs["out_shape"] = None
def decorator(op_func: Callable) -> Callable:
wrapper = CustomOpWrapper(
op_name=op_name or op_func.__name__,
op_func=op_func,
mutates_args=mutates_args or [],
**extra_kwargs,
)
return wrapper.real_impl if eager else wrapper
if fn is not None:
return decorator(fn)
return decorator
class CustomOpWrapper:
def __init__(
self,
op_name: str,
op_func: Callable,
mutates_args: List[str],
**extra_kwargs,
):
self.op_name = op_name
self.op_func = op_func
self.mutates_args = mutates_args
self.extra_kwargs = extra_kwargs
self._impl: Optional[Callable] = None
def __call__(self, *args, **kwargs):
return self.real_impl(*args, **kwargs)
@property
def real_impl(self) -> Callable:
if self._impl is None:
if not hasattr(torch.ops.sglang, self.op_name):
from sglang.srt.utils.common import direct_register_custom_op
# NOTE(dark): if torch compile fail here, mark the decorator as eager
# lazy registration does not work with torch compile
direct_register_custom_op(
op_name=self.op_name,
op_func=self.op_func,
mutates_args=self.mutates_args,
fake_impl=self.fake_impl,
)
self._impl = debug_torch_op(self.op_func, self.op_name)
assert self._impl is not None
return self._impl
@property
def fake_impl(self) -> Callable:
if "fake_impl" in self.extra_kwargs:
return self.extra_kwargs["fake_impl"]
assert "out_shape" in self.extra_kwargs
signature = inspect.signature(self.op_func)
out_shape = self.extra_kwargs["out_shape"]
# check out_shape in signature
def fake_impl(*args, **kwargs):
if out_shape is None:
return None
bound = signature.bind(*args, **kwargs)
bound.apply_defaults()
try:
return torch.empty_like(
bound.args[out_shape]
if isinstance(out_shape, int)
else bound.arguments[out_shape]
)
except (IndexError, KeyError):
raise RuntimeError(
f"Cannot find output argument at position `{out_shape}` for "
f"custom operator `{self.op_name}` with signature `{signature}`."
)
return fake_impl
def register_custom_op_from_extern(
fn: Callable,
*,
op_name: Optional[str] = None,
mutates_args: Optional[List[str]] = None,
out_shape: Optional[Union[int, str]] = None,
out_dtype: Optional[torch.dtype] = None,
fake_impl: Optional[Callable] = None,
computed_args: Optional[dict] = None,
) -> Callable:
"""Wrap an external library function as a custom op for torch.compile compatibility.
Use this to wrap functions from external libraries (e.g. flashinfer kernels) that
perform operations incompatible with torch.compile/dynamo tracing, such as JIT
compilation, file I/O, or dynamic module loading.
The wrapped function becomes an opaque node in the compiled graph. Dynamo will
not trace inside it, avoiding tracing failures. A fake implementation is used
for shape/dtype propagation during compilation.
The external function must have type annotations compatible with
``torch.library.infer_schema`` (``torch.Tensor``, ``int``, ``float``, ``bool``,
``Optional[torch.Tensor]``, etc.).
This function is idempotent: calling it multiple times with the same ``op_name``
(or ``fn.__name__``) safely skips re-registration.
Example usage::
from flashinfer.fused_moe import trtllm_fp8_block_scale_moe
trtllm_fp8_block_scale_moe = register_custom_op_from_extern(
trtllm_fp8_block_scale_moe,
out_shape="hidden_states",
out_dtype=torch.bfloat16,
computed_args={
"tune_max_num_tokens": lambda hidden_states, **kw: next_power_of_2(
hidden_states.shape[0]
),
},
)
:param fn: The external function to wrap.
:param op_name: The name of the custom operator.
Defaults to ``fn.__name__``.
:param mutates_args: A list of argument names that are mutated in-place.
Defaults to ``[]``.
:param out_shape: The position (int) or name (str) of the argument whose shape
matches the output tensor. Used to auto-generate a fake
implementation. Set to ``None`` for inplace-only operators.
:param out_dtype: Override the output dtype in the fake implementation.
If ``None``, ``torch.empty_like`` is used (same dtype as the
reference tensor). Useful when the output dtype differs from
the input (e.g. fp8 input -> bf16 output).
:param fake_impl: A custom fake implementation for shape/dtype propagation.
Only one of ``out_shape`` or ``fake_impl`` should be provided.
:param computed_args: A dict mapping argument names to callables. These arguments
are excluded from the custom op schema and computed inside
the op body at runtime. Each callable receives the other
arguments as keyword args and returns the computed value.
Use this for arguments that vary dynamically (e.g.
``tune_max_num_tokens``) to avoid torch.compile recompilation.
:return: The registered custom op callable (``torch.ops.sglang.<op_name>``).
"""
name = op_name or fn.__name__
computed_args = computed_args or {}
assert not (
out_shape is not None and fake_impl is not None
), "Only one of `out_shape` or `fake_impl` should be provided."
# If computed_args specified, create a wrapper with a reduced signature
# that computes the excluded args inside the op body.
if computed_args:
original_fn = fn
original_sig = inspect.signature(fn)
# Build new signature excluding computed args
new_params = [
p
for param_name, p in original_sig.parameters.items()
if param_name not in computed_args
]
new_sig = original_sig.replace(parameters=new_params)
def wrapper(*args, **kwargs):
bound = new_sig.bind(*args, **kwargs)
bound.apply_defaults()
# Compute excluded args from the bound arguments
for arg_name, compute_fn in computed_args.items():
bound.arguments[arg_name] = compute_fn(**bound.arguments)
return original_fn(**bound.arguments)
wrapper.__name__ = fn.__name__
wrapper.__qualname__ = fn.__qualname__
wrapper.__module__ = fn.__module__
wrapper.__signature__ = new_sig # type: ignore[attr-defined]
# Build annotations without computed args, preserving return type
wrapper.__annotations__ = {
k: v
for k, v in getattr(fn, "__annotations__", {}).items()
if k not in computed_args
}
fn = wrapper
# Generate fake_impl from out_shape if needed
fake_sig = inspect.signature(fn)
if fake_impl is None and out_shape is not None:
def _fake_impl(*args, **kwargs):
bound = fake_sig.bind(*args, **kwargs)
bound.apply_defaults()
try:
ref = (
bound.args[out_shape]
if isinstance(out_shape, int)
else bound.arguments[out_shape]
)
except (IndexError, KeyError):
raise RuntimeError(
f"Cannot find output argument at position `{out_shape}` for "
f"external function `{name}` with signature `{fake_sig}`."
)
if out_dtype is not None:
return torch.empty(ref.shape, dtype=out_dtype, device=ref.device)
return torch.empty_like(ref)
fake_impl = _fake_impl
elif fake_impl is None:
fake_impl = lambda *args, **kwargs: None
from sglang.srt.utils.common import direct_register_custom_op
direct_register_custom_op(
op_name=name,
op_func=fn,
mutates_args=mutates_args or [],
fake_impl=fake_impl,
)
return debug_torch_op(fn, name)