[diffusion] platform: support Intel XPU (#17920)

Signed-off-by: sushil.dubey <sushil.dubey@intel.com>
Signed-off-by: Sushil Dubey <sushil.dubey@intel.com>
Co-authored-by: Ma Mingfei <mingfei.ma@intel.com>
This commit is contained in:
sushil Dubey
2026-04-11 15:09:02 +08:00
committed by GitHub
co-authored by Ma Mingfei
parent 3ce72252de
commit e26c73c4e9
13 changed files with 437 additions and 12 deletions
+20
View File
@@ -69,6 +69,25 @@ dependencies = [
]
[project.optional-dependencies]
diffusion = [
"PyYAML==6.0.1",
"cloudpickle==3.1.2",
"diffusers==0.36.0",
"imageio==2.36.0",
"imageio-ffmpeg==0.5.1",
"moviepy>=2.0.0",
"opencv-python==4.10.0.84",
"remote-pdb==2.1.0",
"st_attn==0.0.7 ; platform_machine != 'aarch64' and platform_machine != 'arm64'",
"runai_model_streamer>=0.15.5",
"cache-dit==1.3.0",
"addict==2.4.0",
"av==16.1.0",
"scikit-image==0.25.2",
"trimesh>=4.0.0",
"xatlas",
]
tracing = [
"opentelemetry-api",
"opentelemetry-exporter-otlp",
@@ -93,6 +112,7 @@ test = [
dev = ["sglang[test]"]
all = [
"sglang[diffusion]",
"sglang[tracing]",
]
@@ -338,7 +338,7 @@ def fuse_scale_shift_kernel(
block_l: int = 128,
block_c: int = 128,
):
assert x.is_cuda and scale.is_cuda
assert (x.is_cuda and scale.is_cuda) or (x.is_xpu and scale.is_xpu)
assert x.is_contiguous()
B, L, C = x.shape
@@ -183,17 +183,18 @@ def init_distributed_environment(
rank: int = 0,
distributed_init_method: str = "env://",
local_rank: int = 0,
backend: str = "nccl",
backend: str | None = None,
device_id: torch.device | None = None,
timeout: int | None = None,
):
# Determine the appropriate backend based on the platform
from sglang.multimodal_gen.runtime.platforms import current_platform
if backend == "nccl" and not current_platform.is_cuda_alike():
# Use gloo backend for non-CUDA platforms (MPS, CPU)
backend = "gloo"
logger.info("Using gloo backend for %s platform", current_platform.device_name)
if backend is None:
backend = current_platform.get_torch_distributed_backend_str()
logger.info(
"Using %s backend for %s platform", backend, current_platform.device_name
)
logger.debug(
"world_size=%d rank=%d local_rank=%d "
@@ -211,13 +212,14 @@ def init_distributed_environment(
"distributed environment"
)
# For MPS and MUSA, don't pass device_id as it doesn't support device indices
# For MPS, MUSA, and XPU, don't pass device_id as it doesn't support device indices
extra_args = (
{}
if (
current_platform.is_mps()
or current_platform.is_musa()
or current_platform.is_npu()
or current_platform.is_xpu()
)
else dict(device_id=device_id)
)
@@ -97,6 +97,9 @@ class NewGELU(CustomOp):
def forward_cuda(self, *args, **kwargs) -> Any:
return self.forward_native(*args, **kwargs)
def forward_xpu(self, *args, **kwargs) -> Any:
return self.forward_native(*args, **kwargs)
def forward_native(self, x: torch.Tensor) -> torch.Tensor:
"""PyTorch-native implementation equivalent to forward()."""
c = math.sqrt(2.0 / math.pi)
@@ -112,6 +115,9 @@ class QuickGELU(CustomOp):
def forward_cuda(self, *args, **kwargs) -> Any:
return self.forward_native(*args, **kwargs)
def forward_xpu(self, *args, **kwargs) -> Any:
return self.forward_native(*args, **kwargs)
def forward_native(self, x: torch.Tensor) -> torch.Tensor:
"""PyTorch-native implementation equivalent to forward()."""
return x * torch.sigmoid(1.702 * x)
@@ -0,0 +1,122 @@
# Copied and adapted from: https://github.com/hao-ai-lab/FastVideo
# SPDX-License-Identifier: Apache-2.0
from functools import lru_cache
import torch
from sglang.multimodal_gen.runtime.platforms import AttentionBackendEnum
try:
from sgl_kernel.flash_attn import flash_attn_varlen_func
flash_attn_func = flash_attn_varlen_func
except ImportError as e:
raise e
from sglang.multimodal_gen.runtime.layers.attention.backends.attention_backend import (
AttentionBackend,
AttentionImpl,
AttentionMetadata,
AttentionMetadataBuilder,
)
from sglang.multimodal_gen.runtime.layers.attention.backends.flash_attn import (
FlashAttentionMetadataBuilder,
)
class XPUAttentionBackend(AttentionBackend):
accept_output_buffer: bool = True
@staticmethod
def get_supported_head_sizes() -> list[int]:
return [64, 96, 128, 192, 256]
@staticmethod
def get_enum() -> AttentionBackendEnum:
return AttentionBackendEnum.FA
@staticmethod
def get_impl_cls() -> type["XPUAttentionImpl"]:
return XPUAttentionImpl
@staticmethod
def get_metadata_cls() -> type["AttentionMetadata"]:
"""XPU backend does not require special metadata."""
return AttentionMetadata
@staticmethod
def get_builder_cls() -> type["AttentionMetadataBuilder"]:
return FlashAttentionMetadataBuilder
@lru_cache(maxsize=128)
def _get_cu_seqlens(device_index: int, bsz: int, seqlen: int) -> torch.Tensor:
return torch.arange(
0,
(bsz + 1) * seqlen,
step=seqlen,
device=torch.device("xpu", device_index),
dtype=torch.int32,
)
class XPUAttentionImpl(AttentionImpl):
def __init__(
self,
num_heads: int,
head_size: int,
causal: bool,
softmax_scale: float,
num_kv_heads: int | None = None,
prefix: str = "",
**extra_impl_args,
) -> None:
self.num_heads = num_heads
self.num_kv_heads = num_kv_heads
self.head_size = head_size
self.causal = causal
self.softmax_scale = softmax_scale
def forward(
self,
query: torch.Tensor,
key: torch.Tensor,
value: torch.Tensor,
attn_metadata: AttentionMetadata = None,
*,
return_softmax_lse: bool = False,
):
bsz, seqlen_q, nheads_q, d = tuple(query.shape)
_, seqlen_k, nheads_k, _ = tuple(key.shape)
max_seqlen_q = seqlen_q
max_seqlen_k = seqlen_k
q_ = query.contiguous().reshape(bsz * seqlen_q, nheads_q, d)
k_ = key.contiguous().reshape(bsz * seqlen_k, nheads_k, d)
v_ = value.contiguous().reshape(bsz * seqlen_k, nheads_k, d)
cu_q = _get_cu_seqlens(q_.device.index, bsz, seqlen_q)
cu_k = _get_cu_seqlens(q_.device.index, bsz, seqlen_k)
out = flash_attn_func(
q=q_,
k=k_,
v=v_,
cu_seqlens_q=cu_q,
cu_seqlens_k=cu_k,
max_seqlen_q=max_seqlen_q,
max_seqlen_k=max_seqlen_k,
softmax_scale=self.softmax_scale,
causal=self.causal,
return_softmax_lse=return_softmax_lse,
)
if return_softmax_lse:
out_tensor, softmax_lse = out[:2]
result = out_tensor.reshape(bsz, seqlen_q, nheads_q, d)
return result, softmax_lse
result = out.reshape(bsz, seqlen_q, nheads_q, d)
return result
@@ -33,3 +33,8 @@ class MulAdd(CustomOp):
self, a: torch.Tensor, b: torch.Tensor, c: torch.Tensor, k: int = 0
):
return fuse_scale_shift_kernel(a, b, c, scale_constant=k)
def forward_xpu(
self, a: torch.Tensor, b: torch.Tensor, c: torch.Tensor, k: int = 0
):
return self.forward_native(a, b, c, k=k)
@@ -31,7 +31,8 @@ from sglang.multimodal_gen.runtime.utils.common import get_bool_env_var
_is_cuda = current_platform.is_cuda()
_is_npu = current_platform.is_npu()
_is_musa = current_platform.is_musa()
if _is_cuda:
_is_xpu = current_platform.is_xpu()
if _is_cuda or _is_xpu:
from sgl_kernel import fused_add_rmsnorm, rmsnorm
if _is_npu:
@@ -212,6 +213,27 @@ class RMSNorm(CustomOp):
out = out.view(shape)
return out
def forward_xpu(
self,
x: torch.Tensor,
residual: Optional[torch.Tensor] = None,
) -> Union[torch.Tensor, Tuple[torch.Tensor, torch.Tensor]]:
shape = x.shape
x = x.reshape(-1, shape[-1])
if residual is not None:
residual_shape = residual.shape
residual = residual.view(-1, shape[-1])
if self.variance_size_override is not None:
return self.forward_native(x, residual)
elif residual is not None:
fused_add_rmsnorm(x, residual, self.weight.data, self.variance_epsilon)
return x.view(shape), residual.view(residual_shape)
else:
out = rmsnorm(x, self.weight.data, self.variance_epsilon)
out = out.view(shape)
return out
def extra_repr(self) -> str:
return f"hidden_size={self.hidden_size}, eps={self.variance_epsilon}"
@@ -410,6 +432,11 @@ class _ScaleResidualNormScaleShift(CustomOp):
# so we fall back to the native PyTorch implementation.
return self.forward_native(*args, **kwargs)
def forward_xpu(self, *args, **kwargs):
# XPU does not support CUDA/CUTLASS-based fused kernels yet,
# so we fall back to the native PyTorch implementation.
return self.forward_native(*args, **kwargs)
def forward_native(
self,
residual: torch.Tensor,
@@ -514,6 +541,11 @@ class _NormScaleShift(CustomOp):
# so we fall back to the native PyTorch implementation.
return self.forward_native(*args, **kwargs)
def forward_xpu(self, *args, **kwargs):
# XPU does not support CUDA/CUTLASS-based fused kernels yet,
# so we fall back to the native PyTorch implementation.
return self.forward_native(*args, **kwargs)
def forward_native(
self, x: torch.Tensor, shift: torch.Tensor, scale: torch.Tensor
) -> torch.Tensor:
@@ -231,7 +231,11 @@ class CLIPAttention(nn.Module):
key_states = key_states.transpose(1, 2)
value_states = value_states.transpose(1, 2)
if current_platform.is_rocm() or current_platform.is_musa():
if (
current_platform.is_rocm()
or current_platform.is_musa()
or current_platform.is_xpu()
):
# ROCm: Using both is_causal=True and attn_mask causes NaN.
# Use is_causal=True alone (padding mask not needed for CLIP
# since pooler_output comes from EOS token before padding).
@@ -138,9 +138,31 @@ def musa_platform_plugin() -> str | None:
)
def xpu_platform_plugin() -> str | None:
"""Detect if Intel XPU platform is available."""
is_xpu = False
try:
import torch
# Check if Intel Extension for PyTorch is available and XPU devices exist
if hasattr(torch, "xpu") and torch.xpu.is_available():
device_count = torch.xpu.device_count()
if device_count > 0:
is_xpu = True
logger.info(
"Intel XPU platform is available with %d device(s)", device_count
)
except Exception as e:
logger.info("Intel XPU platform is unavailable: %s", e)
return "sglang.multimodal_gen.runtime.platforms.xpu.XpuPlatform" if is_xpu else None
builtin_platform_plugins = {
"cuda": cuda_platform_plugin,
"rocm": rocm_platform_plugin,
"xpu": xpu_platform_plugin,
"mps": mps_platform_plugin,
"cpu": cpu_platform_plugin,
"npu": npu_platform_plugin,
@@ -157,6 +179,11 @@ def resolve_current_platform_cls_qualname() -> str:
if platform_cls_qualname is not None:
return platform_cls_qualname
# Try Intel XPU
platform_cls_qualname = xpu_platform_plugin()
if platform_cls_qualname is not None:
return platform_cls_qualname
# Fall back to ROCm
platform_cls_qualname = rocm_platform_plugin()
if platform_cls_qualname is not None:
@@ -63,6 +63,7 @@ class PlatformEnum(enum.Enum):
MPS = enum.auto()
NPU = enum.auto()
MUSA = enum.auto()
XPU = enum.auto()
OOT = enum.auto()
UNSPECIFIED = enum.auto()
@@ -283,6 +284,8 @@ class Platform:
return torch.device("cuda", local_rank)
elif self.is_npu():
return torch.device("npu", local_rank)
elif self.is_xpu():
return torch.device("xpu", local_rank)
elif self.is_musa():
return torch.device("musa", local_rank)
elif self.is_mps():
@@ -300,6 +303,8 @@ class Platform:
return "mccl"
elif self.is_mps():
return "gloo"
elif self.is_xpu():
return "xccl"
else:
raise NotImplementedError(
"No Accelerators(AMD/NV/MTT GPU, AMD MI instinct accelerators) available"
@@ -0,0 +1,196 @@
# SPDX-License-Identifier: Apache-2.0
# Intel XPU Platform support for SGLang Diffusion
import torch
from sglang.multimodal_gen import envs
from sglang.multimodal_gen.runtime.platforms.interface import (
AttentionBackendEnum,
DeviceCapability,
Platform,
PlatformEnum,
)
from sglang.multimodal_gen.runtime.utils.logging_utils import init_logger
logger = init_logger(__name__)
class XpuPlatform(Platform):
"""Platform implementation for Intel XPU (Data Center GPU Max, Arc, etc.)."""
_enum = PlatformEnum.XPU
device_name: str = "xpu"
device_type: str = "xpu"
dispatch_key: str = "XPU"
device_control_env_var: str = "ZE_AFFINITY_MASK"
@classmethod
def get_local_torch_device(cls) -> torch.device:
return torch.device(f"xpu:{envs.LOCAL_RANK}")
@classmethod
def get_device_capability(cls, device_id: int = 0) -> DeviceCapability | None:
device = torch.xpu.current_device()
major, minor = torch.ops.sgl_kernel.query_device.default(device)
return DeviceCapability(major=major, minor=minor)
@classmethod
def get_device_name(cls, device_id: int = 0) -> str:
"""Get the name of the Intel XPU device."""
return torch.xpu.get_device_name(device_id)
@classmethod
def get_device_uuid(cls, device_id: int = 0) -> str:
"""Get the UUID of the Intel XPU device."""
props = torch.xpu.get_device_properties(device_id)
return str(props.uuid)
@classmethod
def get_device_total_memory(cls, device_id: int = 0) -> int:
"""Get total memory of the Intel XPU device in bytes."""
props = torch.xpu.get_device_properties(device_id)
return props.total_memory
@classmethod
def is_async_output_supported(cls, enforce_eager: bool | None) -> bool:
"""Check if async output is supported on Intel XPU."""
if enforce_eager:
logger.warning(
"To see benefits of async output processing, disable enforce-eager. "
"Since enforce-eager is enabled, async output processor cannot be used"
)
return False
return True
@classmethod
def log_warnings(cls) -> None:
"""Log any XPU-specific warnings."""
pass
@classmethod
def get_current_memory_usage(
cls, device: torch.types.Device | None = None
) -> float:
"""Get current memory usage on Intel XPU."""
torch.xpu.reset_peak_memory_stats(device)
return float(torch.xpu.max_memory_allocated(device))
@classmethod
def get_available_gpu_memory(
cls,
device_id: int = 0,
distributed: bool = False,
empty_cache: bool = True,
cpu_group=None,
) -> float:
"""Return the available device memory in GiB."""
if not (hasattr(torch, "xpu") and torch.xpu.is_available()):
return 0.0
num_gpus = torch.xpu.device_count()
if device_id < 0 or device_id >= num_gpus:
raise ValueError(f"Invalid XPU device_id={device_id}. num_gpus={num_gpus}")
current = torch.xpu.current_device()
if current != device_id:
logger.warning(
"current device is not %s, but %s; this may cause useless memory allocation for torch XPU context.",
device_id,
current,
)
if empty_cache:
torch.xpu.empty_cache()
used_memory = float(torch.xpu.memory_allocated(device_id))
total_gpu_memory = float(
torch.xpu.get_device_properties(device_id).total_memory
)
free_gpu_memory = max(0.0, total_gpu_memory - used_memory)
if distributed:
import torch.distributed as dist
tensor = torch.tensor(
free_gpu_memory,
dtype=torch.float32,
device=torch.device("xpu", device_id),
)
dist.all_reduce(tensor, op=dist.ReduceOp.MIN, group=cpu_group)
free_gpu_memory = float(tensor.item())
return free_gpu_memory / (1 << 30)
@classmethod
def get_attn_backend_cls_str(
cls,
selected_backend: AttentionBackendEnum | None,
head_size: int,
dtype: torch.dtype,
) -> str:
"""Get the attention backend class string for Intel XPU.
Defaults to XPU backend (requires fp16/bf16 and a supported head size),
falling back to Torch SDPA if constraints are not met.
"""
if selected_backend in (AttentionBackendEnum.FA, None):
if dtype not in (torch.float16, torch.bfloat16):
logger.info(
"XPU attention backend requires fp16/bf16 but got dtype=%s; falling back to Torch SDPA.",
dtype,
)
return "sglang.multimodal_gen.runtime.layers.attention.backends.sdpa.SDPABackend"
try:
from sglang.multimodal_gen.runtime.layers.attention.backends.xpu_backend import ( # noqa: F401
XPUAttentionBackend,
)
supported_sizes = XPUAttentionBackend.get_supported_head_sizes()
if head_size not in supported_sizes:
logger.info(
"XPU attention backend does not support head_size=%d; falling back to Torch SDPA.",
head_size,
)
return "sglang.multimodal_gen.runtime.layers.attention.backends.sdpa.SDPABackend"
logger.info("Using XPU attention backend on Intel XPU.")
return "sglang.multimodal_gen.runtime.layers.attention.backends.xpu_backend.XPUAttentionBackend"
except Exception as e:
logger.warning(
"Failed to import/use XPU attention backend (%s); falling back to Torch SDPA.",
e,
)
return "sglang.multimodal_gen.runtime.layers.attention.backends.sdpa.SDPABackend"
if selected_backend == AttentionBackendEnum.TORCH_SDPA:
logger.info("Using Torch SDPA backend for Intel XPU.")
return "sglang.multimodal_gen.runtime.layers.attention.backends.sdpa.SDPABackend"
if selected_backend in (
AttentionBackendEnum.SLIDING_TILE_ATTN,
AttentionBackendEnum.SAGE_ATTN,
AttentionBackendEnum.SAGE_ATTN_3,
AttentionBackendEnum.VIDEO_SPARSE_ATTN,
AttentionBackendEnum.VMOBA_ATTN,
AttentionBackendEnum.AITER,
):
logger.warning(
f"{selected_backend.name} is not supported on Intel XPU. "
"Falling back to Torch SDPA backend."
)
return "sglang.multimodal_gen.runtime.layers.attention.backends.sdpa.SDPABackend"
# Default fallback
logger.info("Using Torch SDPA backend for Intel XPU (default).")
return (
"sglang.multimodal_gen.runtime.layers.attention.backends.sdpa.SDPABackend"
)
@classmethod
def get_device_communicator_cls(cls) -> str:
"""Get device communicator class for Intel XPU distributed communication."""
# Use base communicator for now; can be updated to use oneCCL-based communicator
return "sglang.multimodal_gen.runtime.distributed.device_communicators.base_device_communicator.DeviceCommunicatorBase"
@@ -256,9 +256,12 @@ def is_host_cpu_x86() -> bool:
def set_cuda_arch():
capability = torch.cuda.get_device_capability()
arch = f"{capability[0]}.{capability[1]}"
os.environ["TORCH_CUDA_ARCH_LIST"] = f"{arch}{'+PTX' if arch == '9.0' else ''}"
"""Set CUDA architecture for compilation. Only applies to CUDA devices."""
if torch.cuda.is_available():
capability = torch.cuda.get_device_capability()
arch = f"{capability[0]}.{capability[1]}"
os.environ["TORCH_CUDA_ARCH_LIST"] = f"{arch}{'+PTX' if arch == '9.0' else ''}"
# For XPU or other platforms, no arch setting needed
# musa
@@ -69,6 +69,9 @@ class SGLDiffusionProfiler:
if current_platform.is_npu():
activities.append(torch_npu.profiler.ProfilerActivity.NPU)
if hasattr(torch, "xpu") and torch.xpu.is_available():
activities.append(torch.profiler.ProfilerActivity.XPU)
common_torch_profiler_args = dict(
activities=activities,
record_shapes=True,