-
Notifications
You must be signed in to change notification settings - Fork 7.6k
[XPU] Make checkpoint_engine worker device-agnostic #32382
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Draft
siju-samuel
wants to merge
1
commit into
sgl-project:main
Choose a base branch
from
siju-samuel:feat/xpu-checkpoint-engine-support
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+146
−6
Draft
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -22,6 +22,8 @@ | |
| import torch | ||
| import zmq | ||
|
|
||
| from sglang.srt.utils import get_device, get_device_module, is_npu | ||
|
|
||
| try: | ||
| from checkpoint_engine.worker import update_weights_from_ipc | ||
| except ImportError: | ||
|
|
@@ -33,6 +35,14 @@ | |
| logger = logging.getLogger(__name__) | ||
|
|
||
|
|
||
| def _accelerator_type() -> str: | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why not get_device, directly ? |
||
| """Active accelerator device type -- ``"cuda"``, ``"xpu"``, ``"npu"``, etc. | ||
|
|
||
| Resolved via SGLang's device-agnostic ``get_device()`` rather than assuming | ||
| CUDA, so each backend is labeled correctly (e.g. NPU is not mistaken for CUDA).""" | ||
| return get_device() | ||
|
|
||
|
|
||
| class SGLangCheckpointEngineWorkerExtension: | ||
| """ | ||
| Worker extension for SGLang to support checkpoint-engine IPC weight updates. | ||
|
|
@@ -100,17 +110,24 @@ def __init__(self, model_runner): | |
| self.model_runner = model_runner | ||
|
|
||
| def get_device_uuid(self) -> str: | ||
| """Get the UUID of current device.""" | ||
| # Get device UUID for current device | ||
| device_id = torch.cuda.current_device() | ||
| """Physical GPU id, matching checkpoint-engine's ParameterServer key. | ||
|
|
||
| Must equal ps.py::_get_physical_gpu_id for the ZMQ handshake to resolve: | ||
| NPU uses an ``NPU-<uuid>`` key, every other accelerator ``GPU-<uuid>``.""" | ||
| if is_npu(): | ||
| from checkpoint_engine.device_utils import npu_generate_uuid | ||
|
|
||
| return f"NPU-{npu_generate_uuid()}" | ||
| device_module = get_device_module() | ||
| device_id = device_module.current_device() | ||
| try: | ||
| return f"GPU-{torch.cuda.get_device_properties(device_id).uuid!s}" | ||
| return f"GPU-{device_module.get_device_properties(device_id).uuid!s}" | ||
| except AssertionError as e: | ||
| raise ValueError(f"Failed to get GPU UUID for device {device_id}") from e | ||
|
|
||
| def get_device_id(self) -> int: | ||
| """Get the device ID.""" | ||
| return torch.cuda.current_device() | ||
| return get_device_module().current_device() | ||
|
|
||
| def get_model_loader(self) -> Callable: | ||
| """Get the model weight loader function.""" | ||
|
|
@@ -130,7 +147,7 @@ def post_hook(): | |
| if quant_method is not None: | ||
| # Move parameters to device if needed for quantization processing | ||
| target_device = torch.device( | ||
| "cuda", torch.cuda.current_device() | ||
| _accelerator_type(), get_device_module().current_device() | ||
| ) | ||
| with device_loading_context(module, target_device): | ||
| quant_method.process_weights_after_loading(module) | ||
|
|
||
122 changes: 122 additions & 0 deletions
122
test/registered/unit/checkpoint_engine/test_checkpoint_engine_worker.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,122 @@ | ||
| """Unit tests for srt/checkpoint_engine/checkpoint_engine_worker.py — no server, no model loading. | ||
|
|
||
| Focus: device resolution so the ZMQ handshake key matches checkpoint-engine's | ||
| ParameterServer (ps.py::_get_physical_gpu_id) on every backend -- ``GPU-<uuid>`` | ||
| for CUDA/XPU and ``NPU-<uuid>`` for NPU. These paths are pure namespace routing | ||
| (``get_device`` / ``get_device_module`` / ``is_npu``) and are fully mockable on CPU. | ||
| """ | ||
|
|
||
| from sglang.test.ci.ci_register import register_cpu_ci, register_xpu_ci | ||
|
|
||
| register_cpu_ci(est_time=5, suite="base-a-test-cpu") | ||
| register_xpu_ci(est_time=30, suite="stage-b-test-1-gpu-xpu") | ||
|
|
||
| import unittest | ||
| from unittest.mock import MagicMock, patch | ||
|
|
||
| import torch | ||
| from sglang.srt.checkpoint_engine.checkpoint_engine_worker import ( | ||
| SGLangCheckpointEngineWorkerExtensionImpl, | ||
| _accelerator_type, | ||
| ) | ||
| from sglang.srt.utils import is_xpu | ||
| from sglang.test.test_utils import CustomTestCase | ||
|
|
||
| _WORKER_MOD = "sglang.srt.checkpoint_engine.checkpoint_engine_worker" | ||
|
|
||
|
|
||
| class TestAcceleratorType(CustomTestCase): | ||
| def test_delegates_to_get_device(self): | ||
| # _accelerator_type must not hardcode cuda; it reports whatever get_device | ||
| # resolves, so NPU is labeled "npu" rather than mistaken for "cuda". | ||
| for dev in ("cuda", "xpu", "npu"): | ||
| with patch(f"{_WORKER_MOD}.get_device", return_value=dev): | ||
| self.assertEqual(_accelerator_type(), dev) | ||
|
|
||
|
|
||
| class TestWorkerDeviceResolution(CustomTestCase): | ||
| """get_device_uuid / get_device_id must route through the active accelerator | ||
| namespace and emit the key the ParameterServer expects.""" | ||
|
|
||
| def _make_worker(self): | ||
| # model_runner is unused by the device-resolution methods under test. | ||
| return SGLangCheckpointEngineWorkerExtensionImpl(model_runner=MagicMock()) | ||
|
|
||
| def _fake_device_module(self, *, current=3, uuid="abcd-1234"): | ||
| mod = MagicMock() | ||
| mod.current_device.return_value = current | ||
| props = MagicMock() | ||
| props.uuid = uuid | ||
| mod.get_device_properties.return_value = props | ||
| return mod | ||
|
|
||
| def test_device_uuid_cuda(self): | ||
| worker = self._make_worker() | ||
| fake = self._fake_device_module(current=0, uuid="cuda-uuid") | ||
| with ( | ||
| patch(f"{_WORKER_MOD}.is_npu", return_value=False), | ||
| patch(f"{_WORKER_MOD}.get_device_module", return_value=fake), | ||
| ): | ||
| self.assertEqual(worker.get_device_uuid(), "GPU-cuda-uuid") | ||
| self.assertEqual(worker.get_device_id(), 0) | ||
|
|
||
| def test_device_uuid_xpu(self): | ||
| worker = self._make_worker() | ||
| fake = self._fake_device_module(current=2, uuid="xpu-uuid") | ||
| with ( | ||
| patch(f"{_WORKER_MOD}.is_npu", return_value=False), | ||
| patch(f"{_WORKER_MOD}.get_device_module", return_value=fake), | ||
| ): | ||
| # XPU shares CUDA's GPU-<uuid> format; only the namespace differs. | ||
| self.assertEqual(worker.get_device_uuid(), "GPU-xpu-uuid") | ||
| self.assertEqual(worker.get_device_id(), 2) | ||
|
|
||
| def test_device_uuid_npu_uses_npu_prefix(self): | ||
| # NPU must NOT be treated as CUDA: the ParameterServer keys it as | ||
| # NPU-<npu_generate_uuid()>, so a GPU-<uuid> key would never resolve. | ||
| worker = self._make_worker() | ||
| with ( | ||
| patch(f"{_WORKER_MOD}.is_npu", return_value=True), | ||
| patch( | ||
| "checkpoint_engine.device_utils.npu_generate_uuid", | ||
| return_value="1.2.3.4-0", | ||
| ), | ||
| ): | ||
| self.assertEqual(worker.get_device_uuid(), "NPU-1.2.3.4-0") | ||
|
|
||
| def test_device_uuid_wraps_assertion_error(self): | ||
| worker = self._make_worker() | ||
| fake = MagicMock() | ||
| fake.current_device.return_value = 1 | ||
| fake.get_device_properties.side_effect = AssertionError("no uuid") | ||
| with ( | ||
| patch(f"{_WORKER_MOD}.is_npu", return_value=False), | ||
| patch(f"{_WORKER_MOD}.get_device_module", return_value=fake), | ||
| self.assertRaises(ValueError), | ||
| ): | ||
| worker.get_device_uuid() | ||
|
|
||
|
|
||
| @unittest.skipUnless(is_xpu(), "requires an Intel XPU") | ||
| class TestWorkerDeviceUuidOnXpu(CustomTestCase): | ||
| """Hardware-gated: the real XPU key must match what checkpoint-engine's | ||
| ParameterServer derives, or the ZMQ handshake silently fails on XPU.""" | ||
|
|
||
| def test_real_uuid_matches_parameter_server(self): | ||
| from checkpoint_engine.device_utils import DeviceManager | ||
| from checkpoint_engine.ps import _get_physical_gpu_id | ||
|
|
||
| worker = SGLangCheckpointEngineWorkerExtensionImpl(model_runner=MagicMock()) | ||
| key = worker.get_device_uuid() | ||
|
|
||
| self.assertTrue(key.startswith("GPU-"), key) | ||
| self.assertEqual(worker.get_device_id(), torch.xpu.current_device()) | ||
|
|
||
| # Independently derived by the ParameterServer side; the two must agree. | ||
| dm = DeviceManager() | ||
| self.assertEqual(dm.device_type, "xpu") | ||
| self.assertEqual(key, _get_physical_gpu_id(dm, torch.xpu.current_device())) | ||
|
|
||
|
|
||
| if __name__ == "__main__": | ||
| unittest.main(verbosity=3) |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
after PR merge and any release let's use/pin it like CUDA