Conversation
Implement register_host_memory / unregister_host_memory on NPU_Accelerator via torch.npu.npurt() (npuHostRegister / npuHostUnregister over aclrtHostRegisterV2 / aclrtHostUnregister), gated on torch_npu >= 2.9.0 with mlock-only fallbacks. Extend the pin-memory tests with NPU cases, make the H2D/D2H benchmark accelerator-agnostic, and document the NPU registration path. Fixes deepspeedai#8531 Signed-off-by: Zizhao Tang <1601677496@qq.com>
| version = _torch_npu_version() | ||
| if version is None: | ||
| return None, "unable to determine the installed torch_npu version" | ||
| if version < _MIN_TORCH_NPU_REGISTER_VERSION: |
There was a problem hiding this comment.
Do we need to check torch_npu' version? I think check npurt is enough.
There was a problem hiding this comment.
Agreed — the version gate could reject builds that actually ship npurt, and the hasattr check alone covers the case we care about. Removed it in b7e6784; the hook now falls back based only on whether torch.npu.npurt is present (plus npurt() initializing successfully).
… version Signed-off-by: Zizhao Tang <1601677496@qq.com>
| # Register natively pinned (posix_memalign + mlock) host memory with the | ||
| # ACL runtime so torch's async copies can use the DMA engine. npurt | ||
| # initializes the runtime itself, so no set_device ordering is needed. | ||
| if address % 4096: |
There was a problem hiding this comment.
We can align address to 4k, remember ping memory always contains all the buffer, and add num_bytes after align.
There was a problem hiding this comment.
Agreed — Done in bb2e596 — the hook rounds the address down to the 4K page boundary and pads num_bytes by the same offset, so the registered range always covers the original buffer exactly. An already-aligned address passes through unchanged (offset 0), and unregister_host_memory applies the same rounding so both sides stay consistent.
Signed-off-by: Zizhao Tang <1601677496@qq.com>
hipudding
left a comment
There was a problem hiding this comment.
Most of these changes already very good, though there are a few minor points I suggest revising.
| # registration still succeeds instead of failing on the driver's opaque | ||
| # internal error. An already-aligned address passes through unchanged | ||
| # (offset 0), and unregister_host_memory rounds down identically. | ||
| offset = address % 4096 |
There was a problem hiding this comment.
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.
Thanks! Done in 8ba87d1 — extracted _align_to_page_boundary(address) and both hooks now call it: register_host_memory uses the aligned address plus the returned offset for the size pad, and unregister_host_memory passes the aligned address back to the driver. The rounding now lives in one place, so the two can't drift apart.
| assert unregistered == [4096] | ||
|
|
||
|
|
||
| def test_npu_unaligned_address_is_extended_to_page_boundary(monkeypatch): |
There was a problem hiding this comment.
Should test unaligned address and aligned address.
There was a problem hiding this comment.
Done in 8ba87d1 — the two tests were consolidated into one parametrized case (test_npu_register_aligns_to_page_boundary) covering both addresses: the page-aligned address (4096) registers unchanged, the unaligned one (4096+1234) is extended down to the boundary with a matching size pad, and unregister rounds down identically in both cases.
…ster hooks Signed-off-by: Zizhao Tang <1601677496@qq.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 8ba87d196e
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
|
||
| 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.
Guard the NPU lookup test on builds without torch.npu
On the regular CPU/CUDA PyTorch builds that run this unmarked unit-test file, torch has no npu attribute. This dereference raises AttributeError before the helper can exercise its intended missing-npurt fallback, so the newly added test fails the non-NPU unit-test suite; install a stub torch.npu for this test or skip it when the backend is absent.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
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.
torch.npu only exists in torch_npu builds, so test_npu_host_copy_lookup_gates dereferenced a missing attribute on the regular CPU/CUDA unit-test suite. Install a stand-in torch.npu for the test so the gate logic is exercised everywhere. Signed-off-by: Zizhao Tang <1601677496@qq.com>
Fixes #8531.
Follow-up to #8283; XPU counterpart tracked in #8315 (attempted in #8322).
Summary
register_host_memory/unregister_host_memoryonNPU_Acceleratorvia the NPU runtime API torch_npu ships:torch.npu.npurt()returns the runtime-API module withnpuHostRegister/npuHostUnregister, thin wrappers overaclrtHostRegisterV2/aclrtHostUnregister. PreviouslyNPU_Acceleratorinherited the base-class no-ops, soDS_PIN_MEMORY_BACKEND=nativebuffers stayed mlock-only on Ascend NPUs and asynchronous copies ran at pageable bandwidth.DS_PIN_MEMORY_REGISTER_DEVICEopt-out.tests/unit/v1/pin_memory/test_pin_memory.pywith NPU cases mirroring the CUDA cudart-mock tests, including the availability fallbacks.benchmarks/pin_memory/h2d_d2h_bench.pyaccelerator-agnostic (it hardcoded a CUDA check and torch'spin_memory=Truefast path) so the before/after bandwidth could be measured on NPU.docs/code-docs/source/memory.rst.Implementation notes
The npurt binding. torch_npu exposes
torch.npu.npurt(), which initializes the runtime if needed and returns the runtime-API module. Using it avoids bindinglibascendcl.soourselves and raises a clearRuntimeErrorwhen the build lacks the module. Two defenses gate the hook, both falling back to mlock-only with a one-time warning:torch.npu.npurtattribute (the hook checks for its presence),npurt()failing to initialize the runtime.Flag choice:
ACL_HOST_REG_MAPPED. Theflagsparameter ofnpuHostRegisteris the CANNACL_HOST_REG_*bitmask (per the npurt docstring andacl_rt.h):MAPPED(0x2) page-locks the range and adds a device mapping,PINNED(0x10000000) only page-locks. Measured through the bench methodology at 64 MiB (10 process-level A/B runs):PINNED-only registrations fell back to mlock-only H2D speed (~8-9 GB/s) in 5/5 runs, whileMAPPEDkept ~23 GB/s (5/6).MAPPEDadditionally requires 4K-aligned addresses (a 64B-offset probe fails withACL_ERROR_RT_DRV_INTERNAL_ERROR, 507899), which DeepSpeed's native allocator guarantees (posix_memalign(..., sysconf(_SC_PAGESIZE), ...)); sizes need no page alignment (probed 400 B to 1 MiB+3, rc=0). As a guard against unaligned callers, the hook extends the range down to the page boundary, padding the size accordingly, instead of relying on the driver's opaque internal error.Failure semantics. A non-zero return code from
npuHostRegisterlogs once and returnsFalse, soNativePinnedMemorykeeps the buffer mlock-only and does not track the address. A failednpuHostUnregisterraises (like the CUDA hook), which makesNativePinnedMemorykeep the allocation alive so the driver never holds a registration for pages later reused bymalloc.Bandwidth on Ascend 910B4
benchmarks/pin_memory/h2d_d2h_bench.py, one full run of the #8283 methodology (--sizes-mib 4 64 --warmup 10 --iters 50), CANN 8.5.0, torch 2.10.0+cpu, torch_npu 2.10.0, Ascend 910B4 (aarch64), shared box:After registration,
torch.is_pinned()on atorch.frombufferview of the range returnsTrue— the torch_npu runtime recognizes the registered range — so the DMA path is confirmed to engage, not just inferred from the numbers.Registration lifts H2D bandwidth from ~9.7 to ~22.7 GB/s at 4 MiB (2.3x) and holds ~23.4 GB/s at 64 MiB, with D2H improving as well (9.4 → 20.6 and 19.6 → 20.9 GB/s). Registered copies match or exceed the torch
pin_memory()arm at every size.Test plan
pre-commit run --fileson touched paths — yapf, flake8, check-torchdist, check-torchcuda, check-license, codespell all passpytest tests/unit/v1/pin_memory/test_pin_memory.py— 24 passed on Ascend 910B4 (18 existing incl. the CUDA cudart-mock tests + 6 new NPU cases: npurt register/unregister calls, unaligned address extended to the page boundary, non-zero rc degrades to mlock, unregister failure raises, missing npurt is a no-op, availability gate table)benchmarks/pin_memory/h2d_d2h_bench.pyon Ascend 910B4 — all three arms run to completion and exit 0 at 4/64 MiBKnown limitation
Some torch_npu builds may ship without the npurt module. The
hasattrcheck degrades to mlock-only with a one-time warning, so registration is only available when the binding is present.