DFLASH support added for XPU (#32798)

Co-authored-by: Ma Mingfei <mingfei.ma@intel.com>
This commit is contained in:
ANSHUMAN TRIPATHY
2026-09-11 10:39:37 +08:00
committed by GitHub
co-authored by Ma Mingfei
parent 690428b470
commit 0fadad8933
7 changed files with 192 additions and 19 deletions
+1
View File
@@ -111,6 +111,7 @@ DRAFT_ATTENTION_BACKEND_CHOICES = [
"triton", "triton",
"ascend", "ascend",
"trtllm_mha", "trtllm_mha",
"intel_xpu",
] ]
DETERMINISTIC_ATTENTION_BACKEND_CHOICES = [ DETERMINISTIC_ATTENTION_BACKEND_CHOICES = [
@@ -5,6 +5,7 @@ import logging
import os import os
from typing import TYPE_CHECKING, Optional from typing import TYPE_CHECKING, Optional
from sglang.srt.arg_groups.choices import DRAFT_ATTENTION_BACKEND_CHOICES
from sglang.srt.arg_groups.overrides import ( from sglang.srt.arg_groups.overrides import (
_speculative_moe_runner_default, _speculative_moe_runner_default,
attention_backends_of, attention_backends_of,
@@ -198,9 +199,11 @@ def handle_speculative_decoding(server_args: ServerArgs) -> None:
def _handle_dflash(server_args: ServerArgs) -> None: def _handle_dflash(server_args: ServerArgs) -> None:
cfg = resolving_view(server_args) cfg = resolving_view(server_args)
if not (cfg.device.startswith("cuda") or cfg.device == "npu"): if not (
cfg.device.startswith("cuda") or cfg.device == "npu" or cfg.device == "xpu"
):
raise ValueError( raise ValueError(
"DFLASH speculative decoding only supports CUDA and NPU devices." "DFLASH speculative decoding only supports CUDA, NPU and XPU devices."
) )
if resolved_view(server_args).enable_dp_attention: if resolved_view(server_args).enable_dp_attention:
@@ -722,16 +725,11 @@ def _resolve_dflash_draft_attention_backend(server_args: ServerArgs) -> None:
""" """
cfg = resolving_view(server_args) cfg = resolving_view(server_args)
supported_draft_backends = ( supported_draft_backends = DRAFT_ATTENTION_BACKEND_CHOICES
"flashinfer", # FlashInfer is CUDA-only; fall back to triton on XPU and ROCm.
"fa3", fallback_backend = (
"fa4", "triton" if (get_platform().is_xpu or get_platform().is_hip) else "flashinfer"
"triton",
"trtllm_mha",
"ascend",
) )
# Use triton on ROCm (no FlashInfer), flashinfer on CUDA.
fallback_backend = "triton" if get_platform().is_hip else "flashinfer"
draft_backend = cfg.speculative_draft_attention_backend draft_backend = cfg.speculative_draft_attention_backend
if draft_backend is None: if draft_backend is None:
@@ -12,6 +12,7 @@ from sglang.srt.layers.attention.flashattention_backend import (
merge_state_v2_wrapper, merge_state_v2_wrapper,
prepare_swa_spec_page_table_triton, prepare_swa_spec_page_table_triton,
) )
from sglang.srt.layers.radix_attention import AttentionType
from sglang.srt.mem_cache.memory_pool import KVWriteLoc from sglang.srt.mem_cache.memory_pool import KVWriteLoc
from sglang.srt.mem_cache.swa_memory_pool import SWAKVPool from sglang.srt.mem_cache.swa_memory_pool import SWAKVPool
from sglang.srt.model_executor.forward_batch_info import ForwardBatch, ForwardMode from sglang.srt.model_executor.forward_batch_info import ForwardBatch, ForwardMode
@@ -553,7 +554,13 @@ class XPUAttentionBackend(AttentionBackend):
# q = q.to(self.kv_cache_dtype) # q = q.to(self.kv_cache_dtype)
# q_rope = q_rope.to(self.kv_cache_dtype) if q_rope is not None else None # q_rope = q_rope.to(self.kv_cache_dtype) if q_rope is not None else None
# k_rope = k_rope.to(self.kv_cache_dtype) if k_rope is not None else None # k_rope = k_rope.to(self.kv_cache_dtype) if k_rope is not None else None
causal = not layer.is_cross_attention # Mirror FlashAttentionBackend: ENCODER_ONLY / bidirectional layers
# (DFLASH draft full_attention) are non-causal, not just cross-attention.
causal = not (
layer.is_cross_attention
or layer.attn_type
in (AttentionType.ENCODER_ONLY, AttentionType.DECODER_BIDIRECTIONAL)
)
# Check if we should use local attention # Check if we should use local attention
use_local_attn = ( use_local_attn = (
@@ -974,7 +981,13 @@ class XPUAttentionBackend(AttentionBackend):
if layer.sliding_window_size is not None and layer.sliding_window_size > -1 if layer.sliding_window_size is not None and layer.sliding_window_size > -1
else (-1, -1) else (-1, -1)
) )
causal = not layer.is_cross_attention # Mirror FlashAttentionBackend: ENCODER_ONLY / bidirectional layers
# (DFLASH draft full_attention) are non-causal, not just cross-attention.
causal = not (
layer.is_cross_attention
or layer.attn_type
in (AttentionType.ENCODER_ONLY, AttentionType.DECODER_BIDIRECTIONAL)
)
# For fa3 interface version compatibility, we put new fields into conditional keyword args # For fa3 interface version compatibility, we put new fields into conditional keyword args
kwargs = {} kwargs = {}
@@ -36,6 +36,7 @@ _DFLASH_VERIFY_SKIP_CUSTOM_MASK_BACKENDS = frozenset(
"TritonAttnBackend", "TritonAttnBackend",
"TRTLLMHAAttnBackend", "TRTLLMHAAttnBackend",
"TRTLLMMLABackend", "TRTLLMMLABackend",
"XPUAttentionBackend",
} }
) )
@@ -82,7 +82,7 @@ from sglang.srt.speculative.spec_utils import (
assign_req_to_token_pool_func, assign_req_to_token_pool_func,
build_grammar_vocab_mask, build_grammar_vocab_mask,
) )
from sglang.srt.utils import is_cuda, is_hip, is_npu from sglang.srt.utils import is_cuda, is_hip, is_npu, is_xpu
_is_npu = is_npu() _is_npu = is_npu()
@@ -503,12 +503,12 @@ class DFlashWorkerV2(BaseSpecWorker):
self._draft_greedy_rank_index_buf: Optional[torch.Tensor] = None self._draft_greedy_rank_index_buf: Optional[torch.Tensor] = None
self._draft_greedy_selected_ids_buf: Optional[torch.Tensor] = None self._draft_greedy_selected_ids_buf: Optional[torch.Tensor] = None
self._draft_greedy_index_cap: int = 0 self._draft_greedy_index_cap: int = 0
self._use_fused_kv_materialize = is_cuda() or is_hip() self._use_fused_kv_materialize = is_cuda() or is_hip() or is_xpu()
self._fused_kv_helper: Optional[object] = None self._fused_kv_helper: Optional[object] = None
if self._use_fused_kv_materialize: if self._use_fused_kv_materialize:
self._init_fused_kv_helper() self._init_fused_kv_helper()
supports_gpu_triton = is_cuda() or is_hip() supports_gpu_triton = is_cuda() or is_hip() or is_xpu()
self._use_triton_prepare_block = supports_gpu_triton self._use_triton_prepare_block = supports_gpu_triton
self._use_triton_accept_bonus = supports_gpu_triton self._use_triton_accept_bonus = supports_gpu_triton
# The legacy compact-rebuild path host-syncs twice per step (masked # The legacy compact-rebuild path host-syncs twice per step (masked
@@ -9,7 +9,7 @@ import torch
from sglang.srt.layers.logits_processor import LogitsProcessorOutput from sglang.srt.layers.logits_processor import LogitsProcessorOutput
from sglang.srt.managers.tp_worker import TpModelWorker from sglang.srt.managers.tp_worker import TpModelWorker
from sglang.srt.model_executor.forward_batch_info import CaptureHiddenMode from sglang.srt.model_executor.forward_batch_info import CaptureHiddenMode
from sglang.srt.runtime_context import attention_backends, get_spec from sglang.srt.runtime_context import attention_backends, get_platform, get_spec
from sglang.srt.server_args import DRAFT_ATTENTION_BACKEND_CHOICES, ServerArgs from sglang.srt.server_args import DRAFT_ATTENTION_BACKEND_CHOICES, ServerArgs
from sglang.srt.speculative.dflash_info import DFlashVerifyInput from sglang.srt.speculative.dflash_info import DFlashVerifyInput
from sglang.srt.speculative.dflash_info_v2 import DFlashDraftInputV2 from sglang.srt.speculative.dflash_info_v2 import DFlashDraftInputV2
@@ -36,13 +36,17 @@ def _resolve_draft_attention_backend_fallback(*, algo_label: str) -> str:
otherwise the process's prefill backend. Both are resolution's answers, so otherwise the process's prefill backend. Both are resolution's answers, so
they come from the bags. they come from the bags.
""" """
# FlashInfer is CUDA-only; fall back to triton on XPU and ROCm.
platform_fallback = (
"triton" if (get_platform().is_xpu or torch.version.hip) else "flashinfer"
)
draft_backend = get_spec().speculative_draft_attention_backend draft_backend = get_spec().speculative_draft_attention_backend
if draft_backend is None: if draft_backend is None:
draft_backend, _ = attention_backends() draft_backend, _ = attention_backends()
if draft_backend is None: if draft_backend is None:
return "triton" if torch.version.hip else "flashinfer" return platform_fallback
if draft_backend not in DRAFT_ATTENTION_BACKEND_CHOICES: if draft_backend not in DRAFT_ATTENTION_BACKEND_CHOICES:
fallback = "triton" if torch.version.hip else "flashinfer" fallback = platform_fallback
logger.warning( logger.warning(
"%s draft worker only supports attention_backend in %s for now, " "%s draft worker only supports attention_backend in %s for now, "
"but got %r. Falling back to '%s'.", "but got %r. Falling back to '%s'.",
@@ -0,0 +1,156 @@
"""DFLASH speculative decoding on Intel XPU."""
import os
import sys
import unittest
from sglang.srt.utils.common import is_xpu
# Put the `test/` root on sys.path so `registered.<...>` resolves regardless of
# cwd: CI runs each file as `python3 <full_path>` (only the file's own dir is on
# the path), and pytest inserts only the file's dir too. `test/` is three levels
# up from this file's dir (test/registered/xpu/e2e/<this>).
_TEST_ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", ".."))
if _TEST_ROOT not in sys.path:
sys.path.insert(0, _TEST_ROOT)
# Reference the base as a module attribute rather than importing the `Test*`
# name directly: pytest collects by class __name__, so a bare
# `from ... import TestDFlashServerBase` (even aliased) would make it re-collect
# the base's CUDA/flashinfer config here. Only the XPU subclass should run.
from registered.spec.dflash import test_dflash as _dflash_base
from sglang.test.ci.ci_register import register_xpu_ci
register_xpu_ci(est_time=600, suite="nightly-xpu-1-gpu", nightly=True)
# Appended after the base launch_args by setUpClass: the trailing
# --mem-fraction-static overrides the base 0.7, and --device selects the Intel
# GPU. Variants that need extra flags must prepend these (the base does not
# merge other_launch_args — the subclass value replaces it wholesale).
_XPU_LAUNCH_ARGS = ["--device", "xpu", "--mem-fraction-static", "0.75"]
@unittest.skipUnless(is_xpu(), "Intel XPU not available")
class TestDFlashIntelXPU(_dflash_base.TestDFlashServerBase):
"""Full DFLASH suite on device=xpu with the triton attention backend."""
max_running_requests = 8
attention_backend = "triton"
other_launch_args = _XPU_LAUNCH_ARGS
@unittest.skipUnless(is_xpu(), "Intel XPU not available")
class TestDFlashIntelXPUPage256(_dflash_base.TestDFlashServerPage256):
"""page_size=256 + radix-attention smoke test on XPU."""
max_running_requests = 8
attention_backend = "triton"
other_launch_args = _XPU_LAUNCH_ARGS
@unittest.skipUnless(is_xpu(), "Intel XPU not available")
class TestDFlashIntelXPUChunkedPrefill(_dflash_base.TestDFlashServerChunkedPrefill):
"""Chunked prefill (size 4) on XPU."""
max_running_requests = 8
attention_backend = "triton"
# XPU args first, then the variant's own --chunked-prefill-size.
other_launch_args = _XPU_LAUNCH_ARGS + ["--chunked-prefill-size", "4"]
@unittest.skipUnless(is_xpu(), "Intel XPU not available")
class TestDFlashIntelXPUDecodeGraph(_dflash_base.TestDFlashServerBase):
"""Decode CUDA-graph enabled on XPU (opt-in via --cuda-graph-backend-decode)."""
max_running_requests = 8
attention_backend = "triton"
other_launch_args = _XPU_LAUNCH_ARGS + ["--cuda-graph-backend-decode", "full"]
@unittest.skipUnless(is_xpu(), "Intel XPU not available")
class TestDFlashIntelXPUOverlap(_dflash_base.TestDFlashServerOverlap):
"""Overlap schedule enabled on XPU."""
max_running_requests = 8
attention_backend = "triton"
other_launch_args = _XPU_LAUNCH_ARGS
@unittest.skipUnless(is_xpu(), "Intel XPU not available")
class TestDFlashIntelXPUOverlapPlanStream(
_dflash_base.TestDFlashServerOverlapPlanStream
):
"""Overlap schedule with the plan stream on XPU."""
max_running_requests = 8
attention_backend = "triton"
other_launch_args = _XPU_LAUNCH_ARGS
# --- Native XPUAttentionBackend (intel_xpu) variants --------------------------
# Same configs as above, but exercising the native intel_xpu backend rather than
# triton. intel_xpu is not the XPU default (triton is), so it must be selected
# explicitly; these guard the DFLASH draft/verify path through XPUAttentionBackend.
@unittest.skipUnless(is_xpu(), "Intel XPU not available")
class TestDFlashIntelXPUBackend(_dflash_base.TestDFlashServerBase):
"""Full DFLASH suite on device=xpu with the intel_xpu attention backend."""
max_running_requests = 8
attention_backend = "intel_xpu"
other_launch_args = _XPU_LAUNCH_ARGS
@unittest.skipUnless(is_xpu(), "Intel XPU not available")
class TestDFlashIntelXPUBackendPage256(_dflash_base.TestDFlashServerPage256):
"""page_size=256 + radix-attention smoke test on XPU (intel_xpu backend)."""
max_running_requests = 8
attention_backend = "intel_xpu"
other_launch_args = _XPU_LAUNCH_ARGS
@unittest.skipUnless(is_xpu(), "Intel XPU not available")
class TestDFlashIntelXPUBackendChunkedPrefill(
_dflash_base.TestDFlashServerChunkedPrefill
):
"""Chunked prefill (size 128) on XPU (intel_xpu backend)."""
max_running_requests = 8
attention_backend = "intel_xpu"
other_launch_args = _XPU_LAUNCH_ARGS + ["--chunked-prefill-size", "128"]
@unittest.skipUnless(is_xpu(), "Intel XPU not available")
class TestDFlashIntelXPUBackendNoCudaGraph(_dflash_base.TestDFlashServerNoCudaGraph):
"""CUDA-graph disabled on XPU (intel_xpu backend)."""
max_running_requests = 8
attention_backend = "intel_xpu"
other_launch_args = _XPU_LAUNCH_ARGS + ["--disable-cuda-graph"]
@unittest.skipUnless(is_xpu(), "Intel XPU not available")
class TestDFlashIntelXPUBackendOverlap(_dflash_base.TestDFlashServerOverlap):
"""Overlap schedule enabled on XPU (intel_xpu backend)."""
max_running_requests = 8
attention_backend = "intel_xpu"
other_launch_args = _XPU_LAUNCH_ARGS
@unittest.skipUnless(is_xpu(), "Intel XPU not available")
class TestDFlashIntelXPUBackendOverlapPlanStream(
_dflash_base.TestDFlashServerOverlapPlanStream
):
"""Overlap schedule with the plan stream on XPU (intel_xpu backend)."""
max_running_requests = 8
attention_backend = "intel_xpu"
other_launch_args = _XPU_LAUNCH_ARGS
if __name__ == "__main__":
unittest.main()