-
Notifications
You must be signed in to change notification settings - Fork 5k
Register native pinned host memory with the CANN runtime for NPU #8532
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
Changes from 4 commits
efc6845
b7e6784
bb2e596
09f8306
8ba87d1
91d7f01
1155442
128e78c
e00882a
ff9e91b
bca3d15
bba5a0f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -413,8 +413,10 @@ Native device registration | |
|
|
||
| Native allocations are device-independent ``mlock`` buffers. On CUDA systems, | ||
| DeepSpeed additionally calls ``cudaHostRegister`` so PyTorch can use them for | ||
| asynchronous H2D/D2H DMA. Device registration is enabled by default and can be | ||
| disabled for comparison or debugging: | ||
| asynchronous H2D/D2H DMA. On Ascend NPU systems, DeepSpeed additionally calls | ||
|
Collaborator
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. instead of enumerate what each accelerator do, its better to generize the original CUDA behavior description to make it work for more general concept 'accelerator'.
Contributor
Author
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. Thanks @delock, agreed — the per-device enumeration doesn't scale. I've rewritten the paragraph to be |
||
| ``aclrtHostRegisterV2`` / ``aclrtHostUnregister`` for the same purpose. Device | ||
| registration is enabled by default and can be disabled for comparison or | ||
| debugging: | ||
|
|
||
| .. code-block:: bash | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -5,8 +5,10 @@ | |
| import pytest | ||
| import torch | ||
|
|
||
| from deepspeed.accelerator import npu_accelerator | ||
| from deepspeed.accelerator.cpu_accelerator import CPU_Accelerator | ||
| from deepspeed.accelerator.cuda_accelerator import CUDA_Accelerator | ||
| from deepspeed.accelerator.npu_accelerator import NPU_Accelerator | ||
| from deepspeed.utils.pin_memory import NativePinnedMemory | ||
|
|
||
|
|
||
|
|
@@ -284,3 +286,115 @@ def unregister_host_memory(self, address): | |
| assert native_pins.unpin(pinned) is True | ||
| assert accelerator.unregistered == [begin] | ||
| assert begin not in native_pins._device_registered | ||
|
|
||
|
|
||
| def test_npu_device_registration_calls_npurt(monkeypatch): | ||
| registered = [] | ||
| unregistered = [] | ||
|
|
||
| def register(address, num_bytes, flag): | ||
| registered.append((address, num_bytes, flag)) | ||
| return 0 | ||
|
|
||
| def unregister(address): | ||
| unregistered.append(address) | ||
| return 0 | ||
|
|
||
| monkeypatch.setattr(npu_accelerator, "_npu_host_copy_funcs", lambda: ((register, unregister), None)) | ||
| accelerator = NPU_Accelerator.__new__(NPU_Accelerator) | ||
|
|
||
| # 4096: page-aligned addresses register without any range extension. | ||
| assert accelerator.register_host_memory(4096, 4096) is True | ||
| accelerator.unregister_host_memory(4096) | ||
| assert registered == [(4096, 4096, npu_accelerator.ACL_HOST_REG_MAPPED)] | ||
| assert unregistered == [4096] | ||
|
|
||
|
|
||
| def test_npu_unaligned_address_is_extended_to_page_boundary(monkeypatch): | ||
|
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. Should test unaligned address and aligned address.
Contributor
Author
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. Done in 8ba87d1 — the two tests were consolidated into one parametrized case ( |
||
| # MAPPED registration requires 4K-aligned addresses; unaligned addresses | ||
| # are extended down to the page boundary with a matching size pad so the | ||
| # registration still succeeds, and unregister rounds down identically. | ||
| registered = [] | ||
| unregistered = [] | ||
|
|
||
| def register(address, num_bytes, flag): | ||
| registered.append((address, num_bytes, flag)) | ||
| return 0 | ||
|
|
||
| def unregister(address): | ||
| unregistered.append(address) | ||
| return 0 | ||
|
|
||
| monkeypatch.setattr(npu_accelerator, "_npu_host_copy_funcs", lambda: ((register, unregister), None)) | ||
| accelerator = NPU_Accelerator.__new__(NPU_Accelerator) | ||
|
|
||
| assert accelerator.register_host_memory(4096 + 1234, 4096) is True | ||
| assert registered == [(4096, 4096 + 1234, npu_accelerator.ACL_HOST_REG_MAPPED)] | ||
| accelerator.unregister_host_memory(4096 + 1234) | ||
| assert unregistered == [4096] | ||
|
|
||
|
|
||
| def test_npu_device_registration_failure_returns_false(monkeypatch): | ||
| # A non-zero npuHostRegister return code must degrade to mlock-only, not | ||
| # raise: the NativePinnedMemory caller only tracks the address on True. | ||
| def register(address, num_bytes, flag): | ||
| return 107000 | ||
|
|
||
| def unregister(address): | ||
| raise AssertionError("unregister must not run when register failed") | ||
|
|
||
| monkeypatch.setattr(npu_accelerator, "_npu_host_copy_funcs", lambda: ((register, unregister), None)) | ||
| accelerator = NPU_Accelerator.__new__(NPU_Accelerator) | ||
|
|
||
| assert accelerator.register_host_memory(4096, 4096) is False | ||
|
|
||
|
|
||
| def test_npu_unregister_failure_raises(monkeypatch): | ||
| # Raising keeps the allocation alive in NativePinnedMemory so the driver | ||
| # never holds a registration for pages later reused by malloc. | ||
| def register(address, num_bytes, flag): | ||
| return 0 | ||
|
|
||
| def unregister(address): | ||
| return 107000 | ||
|
|
||
| monkeypatch.setattr(npu_accelerator, "_npu_host_copy_funcs", lambda: ((register, unregister), None)) | ||
| accelerator = NPU_Accelerator.__new__(NPU_Accelerator) | ||
|
|
||
| with pytest.raises(RuntimeError, match="npuHostUnregister"): | ||
| accelerator.unregister_host_memory(4096) | ||
|
|
||
|
|
||
| def test_npu_missing_npurt_is_noop(monkeypatch): | ||
| monkeypatch.setattr(npu_accelerator, "_npu_host_copy_funcs", lambda: (None, "test")) | ||
| accelerator = NPU_Accelerator.__new__(NPU_Accelerator) | ||
|
|
||
| assert accelerator.register_host_memory(4096, 4096) is False | ||
| assert accelerator.unregister_host_memory(4096) is None | ||
|
|
||
|
|
||
| def test_npu_host_copy_lookup_gates(monkeypatch): | ||
| # A build lacking npurt must not resolve, with a reason saying so. | ||
| monkeypatch.delattr(torch.npu, "npurt", raising=False) | ||
|
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.
On the regular CPU/CUDA PyTorch builds that run this unmarked unit-test file, Useful? React with 👍 / 👎.
Contributor
Author
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. Done in 91d7f01 — the test now installs a stand-in torch.npu via monkeypatch before touching npurt, so the gate logic also runs on builds without the backend. |
||
| funcs, reason = npu_accelerator._npu_host_copy_funcs() | ||
| assert funcs is None | ||
| assert "npurt is unavailable" in reason | ||
|
|
||
| # npurt() failing to initialize the runtime resolves to None with a reason. | ||
| def fail_npurt(): | ||
| raise RuntimeError("init failed") | ||
|
|
||
| monkeypatch.setattr(torch.npu, "npurt", fail_npurt, raising=False) | ||
| funcs, reason = npu_accelerator._npu_host_copy_funcs() | ||
| assert funcs is None | ||
| assert "initialize" in reason | ||
|
|
||
| # A working npurt module resolves to its host copy functions. | ||
| class _Npurt: | ||
| npuHostRegister = "register" | ||
| npuHostUnregister = "unregister" | ||
|
|
||
| monkeypatch.setattr(torch.npu, "npurt", lambda: _Npurt(), raising=False) | ||
| funcs, reason = npu_accelerator._npu_host_copy_funcs() | ||
| assert funcs == ("register", "unregister") | ||
| assert reason is None | ||
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.
It's better to make align as a function, register and unregister function can call align func. Avoid inconsistent
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.
Thanks! Done in 8ba87d1 — extracted
_align_to_page_boundary(address)and both hooks now call it:register_host_memoryuses the aligned address plus the returned offset for the size pad, andunregister_host_memorypasses the aligned address back to the driver. The rounding now lives in one place, so the two can't drift apart.