[Intel][XPU][LoRA] Enable LoRA on Intel XPU (#30345)
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -103,18 +103,20 @@ class TestTorchNativeLoRABackend(CustomTestCase):
|
||||
self.lora_ranks, dtype=torch.int32, device=self.device
|
||||
)
|
||||
|
||||
output_offset = torch.tensor(
|
||||
[0, weight_out_dim], dtype=torch.int32, device="cpu"
|
||||
)
|
||||
|
||||
expect_output = reference_sgmv_expand(
|
||||
x,
|
||||
weights,
|
||||
weight_indices_tensor,
|
||||
seg_len_tensor,
|
||||
lora_ranks_tensor,
|
||||
slice_offsets=torch.tensor(
|
||||
[0, weight_out_dim], dtype=torch.int32, device="cpu"
|
||||
),
|
||||
slice_offsets=output_offset,
|
||||
)
|
||||
|
||||
actual_output = self.backend.run_lora_b_sgemm(x, weights)
|
||||
actual_output = self.backend.run_lora_b_sgemm(x, weights, output_offset)
|
||||
|
||||
self.assertTrue(torch.allclose(actual_output, expect_output))
|
||||
|
||||
@@ -237,7 +239,9 @@ class TestTorchNativeLoRABackend(CustomTestCase):
|
||||
slice_offsets=output_offset,
|
||||
)
|
||||
|
||||
actual_output = self.backend.run_gate_up_lora(x, gate_up_lora_a, gate_up_lora_b)
|
||||
actual_output = self.backend.run_gate_up_lora(
|
||||
x, gate_up_lora_a, gate_up_lora_b, output_offset
|
||||
)
|
||||
self.assertTrue(torch.allclose(actual_output, expect_output))
|
||||
|
||||
|
||||
|
||||
@@ -11,7 +11,8 @@ from sglang.kernels.ops.moe.fused_moe_lora_kernel import fused_moe_lora
|
||||
# IMPORT PREBUILT KERNEL
|
||||
# ==============================================================================
|
||||
from sglang.kernels.ops.moe.moe_lora_align import moe_lora_align_block_size
|
||||
from sglang.srt.utils import set_random_seed
|
||||
from sglang.srt.lora.lora_moe_runners import _naive_moe_lora_align_block_size
|
||||
from sglang.srt.utils import get_device, is_xpu, set_random_seed
|
||||
from sglang.test.ci.ci_register import register_cuda_ci
|
||||
|
||||
# ==============================================================================
|
||||
@@ -160,23 +161,40 @@ def use_fused_moe_lora_kernel(
|
||||
adapter_enabled = torch.ones(max_loras + 1, dtype=torch.int32, device=device)
|
||||
lora_ids = torch.arange(max_loras, dtype=torch.int32, device=device)
|
||||
|
||||
# call kernel
|
||||
moe_lora_align_block_size(
|
||||
topk_ids,
|
||||
seg_indptr,
|
||||
req_to_lora,
|
||||
num_experts,
|
||||
block_size,
|
||||
max_loras,
|
||||
max_num_tokens_padded,
|
||||
max_num_m_blocks,
|
||||
sorted_token_ids,
|
||||
expert_ids,
|
||||
num_tokens_post_padded,
|
||||
adapter_enabled,
|
||||
lora_ids,
|
||||
None, # maybe_expert_map
|
||||
)
|
||||
# call kernel — the fused CUDA align kernel exists only for CUDA; on XPU
|
||||
# use the pure-torch native alignment.
|
||||
if is_xpu():
|
||||
sorted_token_ids, expert_ids, num_tokens_post_padded = (
|
||||
_naive_moe_lora_align_block_size(
|
||||
topk_ids,
|
||||
seg_indptr,
|
||||
req_to_lora,
|
||||
num_experts,
|
||||
block_size,
|
||||
max_loras,
|
||||
max_num_tokens_padded,
|
||||
max_num_m_blocks,
|
||||
adapter_enabled,
|
||||
device,
|
||||
)
|
||||
)
|
||||
else:
|
||||
moe_lora_align_block_size(
|
||||
topk_ids,
|
||||
seg_indptr,
|
||||
req_to_lora,
|
||||
num_experts,
|
||||
block_size,
|
||||
max_loras,
|
||||
max_num_tokens_padded,
|
||||
max_num_m_blocks,
|
||||
sorted_token_ids,
|
||||
expert_ids,
|
||||
num_tokens_post_padded,
|
||||
adapter_enabled,
|
||||
lora_ids,
|
||||
None, # maybe_expert_map
|
||||
)
|
||||
|
||||
config = {
|
||||
"BLOCK_SIZE_M": 16,
|
||||
@@ -264,7 +282,7 @@ def use_torch(
|
||||
|
||||
|
||||
DTYPES = [torch.float32, torch.float16, torch.bfloat16]
|
||||
DEVICES = [f"cuda:{0}"]
|
||||
DEVICES = [get_device(0)]
|
||||
SEED = [42]
|
||||
|
||||
|
||||
|
||||
@@ -284,7 +284,29 @@ REFERENCE_STATS = {
|
||||
|
||||
|
||||
class TestMoELoraRegression(unittest.TestCase):
|
||||
def test_sglang_moe_parity_strict(self):
|
||||
def test_sglang_moe_parity_flashinfer(self):
|
||||
# flashinfer is CUDA-only.
|
||||
from sglang.srt.utils import is_cuda
|
||||
|
||||
if not is_cuda():
|
||||
self.skipTest("flashinfer backend requires CUDA")
|
||||
self._run_parity_strict(attention_backend="flashinfer")
|
||||
|
||||
def test_sglang_moe_parity_intel_xpu(self):
|
||||
from sglang.srt.utils import is_xpu
|
||||
|
||||
if not is_xpu():
|
||||
self.skipTest("intel_xpu backend requires XPU")
|
||||
self._run_parity_strict(attention_backend="intel_xpu")
|
||||
|
||||
def test_sglang_moe_parity_triton(self):
|
||||
from sglang.srt.utils import is_cuda, is_xpu
|
||||
|
||||
if is_cuda() or is_xpu():
|
||||
self.skipTest("triton backend is the fallback for non-CUDA/non-XPU")
|
||||
self._run_parity_strict(attention_backend="triton")
|
||||
|
||||
def _run_parity_strict(self, *, attention_backend, **runner_kwargs):
|
||||
|
||||
with SRTRunner(
|
||||
model_path=MOE_BASE_MODEL_PATH,
|
||||
@@ -295,8 +317,9 @@ class TestMoELoraRegression(unittest.TestCase):
|
||||
tp_size=1,
|
||||
trust_remote_code=True,
|
||||
disable_radix_cache=True,
|
||||
attention_backend="flashinfer",
|
||||
attention_backend=attention_backend,
|
||||
mem_fraction_static=0.80,
|
||||
**runner_kwargs,
|
||||
) as srt_runner:
|
||||
srt_outputs = srt_runner.forward(
|
||||
MOE_LORA_TEST_PROMPTS,
|
||||
|
||||
@@ -23,6 +23,7 @@ from torch.cuda import Stream as CudaStream
|
||||
|
||||
from sglang.srt.lora.lora_manager import LoRAManager
|
||||
from sglang.srt.lora.lora_overlap_loader import LoRAOverlapLoader, LoRAOverlapLoadStatus
|
||||
from sglang.srt.utils import get_device
|
||||
from sglang.test.ci.ci_register import register_amd_ci, register_cuda_ci
|
||||
from sglang.test.lora_utils import (
|
||||
CI_MULTI_LORA_MODELS,
|
||||
@@ -33,6 +34,8 @@ from sglang.test.test_utils import CustomTestCase
|
||||
register_cuda_ci(est_time=237, stage="base-b", runner_config="1-gpu-large")
|
||||
register_amd_ci(est_time=75, suite="stage-b-test-1-gpu-small-amd")
|
||||
|
||||
DEVICE = get_device(0)
|
||||
|
||||
|
||||
class TestLoRAOverlapLoading(CustomTestCase):
|
||||
def test_ci_lora_models_batch_splitting(self):
|
||||
@@ -56,6 +59,8 @@ class TestLoRAOverlapLoaderUnitTests(CustomTestCase):
|
||||
self.mock_stream = MagicMock(spec=CudaStream)
|
||||
self.mock_stream_context = MagicMock()
|
||||
self.mock_event = MagicMock(spec=CudaEvent)
|
||||
# Default to an in-flight copy; cases needing completion flip query().
|
||||
self.mock_event.query.return_value = False
|
||||
|
||||
self.mock_device_module.Stream.return_value = self.mock_stream
|
||||
self.mock_device_module.stream.return_value = self.mock_stream_context
|
||||
@@ -64,7 +69,7 @@ class TestLoRAOverlapLoaderUnitTests(CustomTestCase):
|
||||
self.mock_torch.cuda.current_stream.return_value = MagicMock(spec=CudaStream)
|
||||
|
||||
self.mock_lora_manager = MagicMock(spec=LoRAManager)
|
||||
self.mock_lora_manager.device = "cuda:0"
|
||||
self.mock_lora_manager.device = DEVICE
|
||||
self.mock_lora_manager.memory_pool = MagicMock()
|
||||
self.mock_lora_manager.memory_pool.uid_to_buffer_id = {}
|
||||
self.mock_lora_manager.validate_lora_batch.return_value = True
|
||||
@@ -166,7 +171,7 @@ class TestLoRAOverlapLoaderUnitTests(CustomTestCase):
|
||||
|
||||
def test_pending_load_is_synchronized_before_unload(self):
|
||||
manager = LoRAManager.__new__(LoRAManager)
|
||||
manager.device = torch.device("cuda:0")
|
||||
manager.device = torch.device(DEVICE)
|
||||
manager.pending_lora_load_events = {}
|
||||
manager.memory_pool = MagicMock()
|
||||
manager.configs = {"lora_A": object()}
|
||||
|
||||
@@ -50,6 +50,25 @@ DECODE_ATTENTION_BACKEND = "fa4"
|
||||
KL_THRESHOLD = 5e-3
|
||||
|
||||
|
||||
def attention_backend_kwargs():
|
||||
"""Engine attention-backend kwargs for the current platform.
|
||||
|
||||
fa4 and flashinfer are CUDA-only: fa4 dispatches into the CUTLASS CUTE DSL
|
||||
kernel, which cannot import off CUDA. On XPU the equivalent fused path is
|
||||
the intel_xpu backend, so select it there instead of forcing a backend the
|
||||
device has no kernels for.
|
||||
"""
|
||||
from sglang.srt.utils import is_xpu
|
||||
|
||||
if is_xpu():
|
||||
return {"attention_backend": "intel_xpu"}
|
||||
return {
|
||||
"attention_backend": "flashinfer",
|
||||
"prefill_attention_backend": PREFILL_ATTENTION_BACKEND,
|
||||
"decode_attention_backend": DECODE_ATTENTION_BACKEND,
|
||||
}
|
||||
|
||||
|
||||
def kl_v2(a, b):
|
||||
a = torch.tensor(a) if not torch.is_tensor(a) else a
|
||||
b = torch.tensor(b) if not torch.is_tensor(b) else b
|
||||
@@ -137,9 +156,7 @@ class TestLoRAQwen3_8BLogprobDiff(CustomTestCase):
|
||||
max_lora_rank=MAX_LORA_RANK,
|
||||
lora_paths={"my_lora": adapter_path},
|
||||
lora_backend=LORA_BACKEND,
|
||||
attention_backend="flashinfer",
|
||||
prefill_attention_backend=PREFILL_ATTENTION_BACKEND,
|
||||
decode_attention_backend=DECODE_ATTENTION_BACKEND,
|
||||
**attention_backend_kwargs(),
|
||||
)
|
||||
|
||||
try:
|
||||
|
||||
@@ -4,10 +4,18 @@ import pytest
|
||||
import torch
|
||||
|
||||
from sglang.srt.lora.backend.base_backend import _compute_moe_lora_info
|
||||
from sglang.test.ci.ci_register import register_amd_ci, register_cuda_ci
|
||||
from sglang.srt.utils import get_device
|
||||
from sglang.test.ci.ci_register import (
|
||||
register_amd_ci,
|
||||
register_cuda_ci,
|
||||
register_xpu_ci,
|
||||
)
|
||||
|
||||
register_cuda_ci(est_time=9, stage="base-b", runner_config="1-gpu-small")
|
||||
register_amd_ci(est_time=5, stage="stage-b", runner_config="1-gpu-small-amd")
|
||||
register_xpu_ci(est_time=20, suite="stage-a-test-1-gpu-xpu")
|
||||
|
||||
DEVICE = get_device()
|
||||
|
||||
|
||||
def _expected_adapter_enabled(
|
||||
@@ -25,7 +33,7 @@ def _expected_adapter_enabled(
|
||||
|
||||
@pytest.mark.parametrize("use_preallocated_buffers", [False, True])
|
||||
def test_compute_moe_lora_info_expands_segments(use_preallocated_buffers: bool):
|
||||
device = "cuda"
|
||||
device = DEVICE
|
||||
seg_lens = torch.tensor([5, 1, 7, 3, 9, 2], dtype=torch.int32, device=device)
|
||||
seg_indptr = torch.zeros((seg_lens.numel() + 1,), dtype=torch.int32, device=device)
|
||||
seg_indptr[1:] = torch.cumsum(seg_lens, dim=0)
|
||||
@@ -54,7 +62,7 @@ def test_compute_moe_lora_info_expands_segments(use_preallocated_buffers: bool):
|
||||
token_lora_mapping,
|
||||
max_len=int(seg_lens.max().item()),
|
||||
)
|
||||
torch.cuda.synchronize()
|
||||
torch.get_device_module(device).synchronize()
|
||||
|
||||
expected_mapping = torch.repeat_interleave(weight_indices, seg_lens)
|
||||
expected_enabled = _expected_adapter_enabled(lora_ranks, weight_indices)
|
||||
@@ -67,7 +75,7 @@ def test_compute_moe_lora_info_expands_segments(use_preallocated_buffers: bool):
|
||||
|
||||
|
||||
def test_compute_moe_lora_info_rejects_undercovered_launch():
|
||||
device = "cuda"
|
||||
device = DEVICE
|
||||
seg_indptr = torch.tensor([0, 300], dtype=torch.int32, device=device)
|
||||
weight_indices = torch.tensor([0], dtype=torch.int32, device=device)
|
||||
lora_ranks = torch.tensor([16], dtype=torch.int32, device=device)
|
||||
|
||||
@@ -15,9 +15,10 @@ Covers two regression bugs that surface only with `--lora-use-virtual-experts`
|
||||
wrap, or past-end) and don't get assigned to a real expert in the
|
||||
consumer-block table.
|
||||
|
||||
Both kernels run on CUDA. The fallback is gated on `virtual_num_experts >= 1024`
|
||||
in production, but we exercise it directly here at smaller sizes for cheaper
|
||||
iteration; one test sticks to the >1024 regime to mirror the production trigger.
|
||||
Both kernels run on CUDA or XPU; only the CUDA-JIT align variant is CUDA-only.
|
||||
The fallback is gated on `virtual_num_experts >= 1024` in production, but we
|
||||
exercise it directly here at smaller sizes for cheaper iteration; one test
|
||||
sticks to the >1024 regime to mirror the production trigger.
|
||||
|
||||
Usage:
|
||||
python -m pytest test/registered/lora/test_virtual_experts_kernels.py -v
|
||||
@@ -27,10 +28,19 @@ import unittest
|
||||
|
||||
import torch
|
||||
|
||||
from sglang.test.ci.ci_register import register_cuda_ci
|
||||
from sglang.srt.utils import get_device, is_cuda, is_xpu
|
||||
from sglang.test.ci.ci_register import register_cuda_ci, register_xpu_ci
|
||||
from sglang.test.test_utils import CustomTestCase
|
||||
|
||||
register_cuda_ci(est_time=14, stage="base-b", runner_config="1-gpu-small")
|
||||
register_xpu_ci(est_time=20, suite="stage-a-test-1-gpu-xpu")
|
||||
|
||||
|
||||
def _require_accelerator_device():
|
||||
if not (is_cuda() or is_xpu()):
|
||||
raise unittest.SkipTest("CUDA or XPU required")
|
||||
return get_device(0)
|
||||
|
||||
|
||||
from sglang.kernels.ops.moe.virtual_experts import (
|
||||
_align_block_size_jit,
|
||||
@@ -45,9 +55,7 @@ class TestFusedVirtualTopkIdsPreservesSentinels(CustomTestCase):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
if not torch.cuda.is_available():
|
||||
raise unittest.SkipTest("CUDA required")
|
||||
cls.device = "cuda:0"
|
||||
cls.device = _require_accelerator_device()
|
||||
|
||||
def test_negative_sentinels_preserved(self):
|
||||
# Mix of valid topk_ids in [0, num_experts), -1 sentinels (typical
|
||||
@@ -139,11 +147,9 @@ class _AlignBlockSizeSentinelBucketBase(CustomTestCase):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
if not torch.cuda.is_available():
|
||||
raise unittest.SkipTest("CUDA required")
|
||||
if cls is _AlignBlockSizeSentinelBucketBase:
|
||||
raise unittest.SkipTest("Base class")
|
||||
cls.device = "cuda:0"
|
||||
cls.device = _require_accelerator_device()
|
||||
|
||||
def _align(self, topk_ids, block_size, num_experts):
|
||||
raise NotImplementedError
|
||||
@@ -264,6 +270,13 @@ class TestAlignBlockSizeTorchSentinelBucket(_AlignBlockSizeSentinelBucketBase):
|
||||
return _align_block_size_torch(topk_ids, block_size, num_experts)
|
||||
|
||||
|
||||
@unittest.skipIf(
|
||||
is_xpu(),
|
||||
"_align_block_size_jit builds a CUDA JIT kernel via tvm_ffi.load_inline "
|
||||
"(requires a CUDA/nvcc install); it cannot run on XPU. The torch variant "
|
||||
"(TestAlignBlockSizeTorchSentinelBucket) covers the same alignment logic "
|
||||
"on that platform.",
|
||||
)
|
||||
class TestAlignBlockSizeJitSentinelBucket(_AlignBlockSizeSentinelBucketBase):
|
||||
"""Test the CUDA JIT kernel path (with fused_sanitize_expert_ids, as in
|
||||
production)."""
|
||||
|
||||
Reference in New Issue
Block a user