Skip to content
Merged
Show file tree
Hide file tree
Changes from 19 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion python/sglang/kernels/ops/layernorm/mhc.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
from sglang.srt.layers.attention.dsa.utils import is_dsa_prefill_cp_round_robin_split
from sglang.srt.layers.dp_attention import is_allocation_symmetric
from sglang.srt.layers.utils.common import strict_contiguous
from sglang.srt.utils import get_device_core_count

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -543,7 +544,11 @@ def _compute_num_split_for_mhc_pre(num_tokens: int, hc_hidden_size: int) -> int:
block_m, block_k = 64, 64
grid_size = (num_tokens + block_m - 1) // block_m
num_block_k = (hc_hidden_size + block_k - 1) // block_k
n_sms = torch.cuda.get_device_properties(0).multi_processor_count

Comment thread
mingfeima marked this conversation as resolved.
Resolved
n_sms = get_device_core_count()
if n_sms <= 0:
n_sms = 1

Comment thread
cyxlily marked this conversation as resolved.
Resolved
return max(1, min(n_sms // max(grid_size, 1), num_block_k // 4))


Expand Down
74 changes: 67 additions & 7 deletions python/sglang/srt/models/deepseek_v4.py
Original file line number Diff line number Diff line change
Expand Up @@ -182,30 +182,53 @@ class MhcOps(NamedTuple):
hc_split_sinkhorn: Callable[..., Any]
mhc_fused_post_pre: Optional[Callable[..., Any]]
npu_hc_pre: Optional[Callable[..., Any]]
mhc_pre: Optional[Callable[..., Any]]
mhc_post: Optional[Callable[..., Any]]
fused_hc_head: Optional[Callable[..., Any]]


@functools.cache
def _get_mhc_ops() -> MhcOps:
"""Load MHC kernels only when a DeepSeek-V4 layer needs them.

Model modules are imported eagerly by the registry. Importing
Model modules are imported eagerly by the registry. Importing
``sglang.kernels.ops.layernorm.mhc`` owns TileLang-backed MHC kernels.
Import it only when a DeepSeek-V4 layer executes so registry discovery
cannot initialize an optional CUDA runtime before unrelated models set up
their communication workspaces. DeepSeek-V4 is the sole consumer here.
their communication workspaces. DeepSeek-V4 is the sole consumer here.
"""
if _is_xpu:
from sgl_kernel import hc_split_sinkhorn
from sgl_kernel import (
fused_hc_head,
hc_post,
hc_split_sinkhorn,
mhc_fused_post_pre,
mhc_pre,
)

return MhcOps(hc_split_sinkhorn, None, None)
return MhcOps(
hc_split_sinkhorn=hc_split_sinkhorn,
mhc_fused_post_pre=mhc_fused_post_pre,
npu_hc_pre=None,
mhc_pre=mhc_pre,
mhc_post=hc_post,
fused_hc_head=fused_hc_head,
)

from sglang.kernels.ops.layernorm.mhc import (
hc_split_sinkhorn,
mhc_fused_post_pre,
npu_hc_pre,
)

return MhcOps(hc_split_sinkhorn, mhc_fused_post_pre, npu_hc_pre)
return MhcOps(
hc_split_sinkhorn=hc_split_sinkhorn,
mhc_fused_post_pre=mhc_fused_post_pre,
npu_hc_pre=npu_hc_pre,
mhc_pre=None,
mhc_post=None,
fused_hc_head=None,
)


logger = logging.getLogger(__name__)
Expand All @@ -220,6 +243,11 @@ def _get_mhc_ops() -> MhcOps:


def _is_fused_mhc_post_pre_enabled() -> bool:
# CUDA/HIP fused path reuses TileLang mhc_post/mhc_pre kernels and their
# tensor layout assumptions. XPU uses the sgl-kernel SYCL fused interface.
if _is_xpu:
return envs.SGLANG_OPT_FUSE_MHC_POST_PRE.get()

# SM120 disables the standalone TileLang pre path. mhc_fused_post_pre does
# not read that flag and dispatches independently for both small and large
# token batches, so the standalone pre flag must not veto the fused opt-in.
Expand Down Expand Up @@ -1831,6 +1859,26 @@ def hc_pre_torch_impl(x, hc_fn):
)
return y, post, comb, False

if _is_xpu:
norm_kwargs = {}
if norm is not None:
norm_kwargs["norm_weight"] = norm.weight.data
norm_kwargs["norm_eps"] = norm.variance_epsilon

post, comb, y = _get_mhc_ops().mhc_pre(
residual=x,
fn=hc_fn,
hc_scale=hc_scale,
hc_base=hc_base,
rms_eps=self.rms_norm_eps,
hc_pre_eps=self.hc_eps,
hc_sinkhorn_eps=self.hc_eps,
hc_post_mult_value=_MHC_POST_MULT_VALUE,
sinkhorn_repeat=self.hc_sinkhorn_iters,
**norm_kwargs,
)
return y, post, comb, norm is not None

if envs.SGLANG_OPT_USE_FLASHINFER_MHC.get():
y, post, comb = _flashinfer_hc_pre(
x,
Expand Down Expand Up @@ -1936,6 +1984,9 @@ def hc_post(
if _is_npu:
return torch.ops.custom.npu_hc_post(x, residual, post, comb)

if _is_xpu:
return _get_mhc_ops().mhc_post(x, residual, post, comb)

if envs.SGLANG_OPT_USE_FLASHINFER_MHC.get():
from flashinfer.mhc import mhc_post

Expand Down Expand Up @@ -2610,6 +2661,15 @@ def hc_head(
hc_base: torch.Tensor,
):
if x.numel() > 0:
if _is_xpu:
return _get_mhc_ops().fused_hc_head(
x.contiguous(),
hc_fn,
hc_scale,
hc_base,
norm_eps=self.norm_eps,
hc_eps=self.hc_eps,
)
from sglang.kernels.ops.layernorm.mhc_head import fused_hc_head

return fused_hc_head(
Expand Down Expand Up @@ -3304,7 +3364,7 @@ def _prewarm_mhc_kernels(self) -> None:
if self._mhc_prewarmed_at_load:
return
self._mhc_prewarmed_at_load = True
if _is_npu or not envs.SGLANG_OPT_USE_TILELANG_MHC_PRE.get():
if _is_npu or _is_xpu or not envs.SGLANG_OPT_USE_TILELANG_MHC_PRE.get():
return
layer = next(
(m for m in self.model.layers if isinstance(m, DeepseekV4DecoderLayer)),
Expand Down Expand Up @@ -3378,7 +3438,7 @@ def load_weights(self, weights: Iterable[Tuple[str, torch.Tensor]], is_nextn=Fal
else:
raise ValueError("num_nextn_predict_layers is not in the config")

if not envs.SGLANG_OPT_FP8_WO_A_GEMM.get():
if not _FP8_WO_A_GEMM:
weights = _dequant_fp8_wo_a_streaming(weights)

stacked_params_mapping = DEEPSEEK_V4_STACKED_PARAMS_MAPPING
Expand Down
Loading