Skip to content

Commit c33542b

Browse files
somtriclaude
andcommitted
fix(cuda.core): pass numa_id to cuMemCreate for host_numa virtual memory
VirtualMemoryResource passed -1 as the location id for host_numa allocations, and cuMemCreate rejects that with CUDA_ERROR_INVALID_VALUE. Add VirtualMemoryResourceOptions.numa_id. It is required for host_numa, rejected for other location types, and must match in a per-call modify_allocation config. Reject host_numa_current at construction, because the driver does not accept it for physical allocations. Closes #2694 Co-authored-by: Claude Opus 5.5 <[email protected]> Signed-off-by: Som Tripathi <[email protected]>
1 parent 576ef68 commit c33542b

5 files changed

Lines changed: 128 additions & 10 deletions

File tree

‎cuda_core/cuda/core/_memory/_virtual_memory_resource.pyi‎

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,9 @@ class VirtualMemoryResourceOptions:
3030
allocation_type: :obj:`~_memory.VirtualMemoryAllocationType` | str
3131
Controls the type of allocation.
3232
location_type: :obj:`~_memory.VirtualMemoryLocationType` | str
33-
Controls the location of the allocation.
33+
Controls the location of the allocation. ``"host_numa"`` requires
34+
``numa_id``. ``"host_numa_current"`` is rejected because the driver
35+
does not accept it for physical allocations.
3436
handle_type: :obj:`~_memory.VirtualMemoryHandleType` | str
3537
Export handle type for the physical allocation, and the switch for
3638
sharing buffers with other processes (see
@@ -52,6 +54,11 @@ class VirtualMemoryResourceOptions:
5254
Access flags for the owning device.
5355
peer_access: :obj:`~_memory.VirtualMemoryAccessType` | None | str
5456
Access flags for peers.
57+
numa_id: int | None
58+
Host NUMA node of the allocation. Required for
59+
``location_type="host_numa"`` and rejected for every other location
60+
type. Must be >= 0; ``Device.properties.host_numa_id`` is the node
61+
closest to a device.
5562
"""
5663
allocation_type: VirtualMemoryAllocationType = VirtualMemoryAllocationType.PINNED
5764
location_type: VirtualMemoryLocationType = VirtualMemoryLocationType.DEVICE
@@ -63,6 +70,7 @@ class VirtualMemoryResourceOptions:
6370
peers: Iterable[int] = field(default_factory=tuple)
6471
self_access: VirtualMemoryAccessType = VirtualMemoryAccessType.READ_WRITE
6572
peer_access: VirtualMemoryAccessType = VirtualMemoryAccessType.READ_WRITE
73+
numa_id: int | None = None
6674
_a = driver.CUmemAccess_flags
6775
_access_flags = {VirtualMemoryAccessType.READ_WRITE: _a.CU_MEM_ACCESS_FLAGS_PROT_READWRITE, VirtualMemoryAccessType.READ: _a.CU_MEM_ACCESS_FLAGS_PROT_READ, None: 0}
6876
_h = driver.CUmemAllocationHandleType

‎cuda_core/cuda/core/_memory/_virtual_memory_resource.pyx‎

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,9 @@ class VirtualMemoryResourceOptions:
9999
allocation_type: :obj:`~_memory.VirtualMemoryAllocationType` | str
100100
Controls the type of allocation.
101101
location_type: :obj:`~_memory.VirtualMemoryLocationType` | str
102-
Controls the location of the allocation.
102+
Controls the location of the allocation. ``"host_numa"`` requires
103+
``numa_id``. ``"host_numa_current"`` is rejected because the driver
104+
does not accept it for physical allocations.
103105
handle_type: :obj:`~_memory.VirtualMemoryHandleType` | str
104106
Export handle type for the physical allocation, and the switch for
105107
sharing buffers with other processes (see
@@ -121,6 +123,11 @@ class VirtualMemoryResourceOptions:
121123
Access flags for the owning device.
122124
peer_access: :obj:`~_memory.VirtualMemoryAccessType` | None | str
123125
Access flags for peers.
126+
numa_id: int | None
127+
Host NUMA node of the allocation. Required for
128+
``location_type="host_numa"`` and rejected for every other location
129+
type. Must be >= 0; ``Device.properties.host_numa_id`` is the node
130+
closest to a device.
124131
"""
125132

126133
allocation_type: VirtualMemoryAllocationType = VirtualMemoryAllocationType.PINNED
@@ -133,6 +140,7 @@ class VirtualMemoryResourceOptions:
133140
peers: Iterable[int] = field(default_factory=tuple)
134141
self_access: VirtualMemoryAccessType = VirtualMemoryAccessType.READ_WRITE
135142
peer_access: VirtualMemoryAccessType = VirtualMemoryAccessType.READ_WRITE
143+
numa_id: int | None = None
136144

137145
_a = driver.CUmemAccess_flags
138146
_access_flags = { # noqa: RUF012
@@ -472,6 +480,29 @@ cdef class VirtualMemoryResource(MemoryResource):
472480
f"config.location_type {str(cfg.location_type)!r} does not match the resource's "
473481
f"{str(self.config.location_type)!r}; the location of a buffer cannot change"
474482
)
483+
if cfg.numa_id != self.config.numa_id:
484+
raise ValueError(
485+
f"config.numa_id {cfg.numa_id!r} does not match the resource's "
486+
f"{self.config.numa_id!r}; the location of a buffer cannot change"
487+
)
488+
if cfg.location_type == VirtualMemoryLocationType.HOST_NUMA_CURRENT:
489+
raise ValueError(
490+
'location_type="host_numa_current" is not supported: the driver does not accept it '
491+
'for physical allocations; use location_type="host_numa" with numa_id'
492+
)
493+
if cfg.location_type == VirtualMemoryLocationType.HOST_NUMA:
494+
if cfg.numa_id is None:
495+
raise ValueError(
496+
'location_type="host_numa" requires numa_id; pass numa_id=<host NUMA node id>, '
497+
"e.g. device.properties.host_numa_id"
498+
)
499+
if cfg.numa_id < 0:
500+
raise ValueError(f"numa_id must be >= 0, got {cfg.numa_id}")
501+
elif cfg.numa_id is not None:
502+
raise ValueError(
503+
'numa_id is only valid with location_type="host_numa", '
504+
f"got location_type={str(cfg.location_type)!r}"
505+
)
475506
# The driver rejects an exportable handle type for HOST memory only;
476507
# the NUMA location types may carry one.
477508
if cfg.location_type == VirtualMemoryLocationType.HOST and cfg.handle_type is not None:
@@ -499,7 +530,10 @@ cdef class VirtualMemoryResource(MemoryResource):
499530
memset(prop, 0, sizeof(cydriver.CUmemAllocationProp))
500531
prop.type = <cydriver.CUmemAllocationType>alloc_type
501532
prop.location.type = <cydriver.CUmemLocationType>loc_type
502-
prop.location.id = self.device.device_id if self.device is not None else -1
533+
if self.config.location_type == VirtualMemoryLocationType.HOST_NUMA:
534+
prop.location.id = self.config.numa_id
535+
else:
536+
prop.location.id = self.device.device_id if self.device is not None else -1
503537
prop.allocFlags.gpuDirectRDMACapable = 1 if cfg.gpu_direct_rdma else 0
504538
prop.requestedHandleTypes = <cydriver.CUmemAllocationHandleType>handle_type
505539
prop.win32HandleMetaData = NULL

‎cuda_core/docs/source/release/1.3.0-notes.rst‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -303,3 +303,14 @@ Fixes and enhancements
303303
while operands from plain ``cuMemAlloc`` allocations accept it. The node's
304304
``repr`` now shows ``U`` for unified operands.
305305
(`#2649 <https://github.com/NVIDIA/cuda-python/issues/2649>`__)
306+
307+
- :class:`VirtualMemoryResource` can now allocate with
308+
``location_type="host_numa"``. Every such allocation failed with
309+
``CUDA_ERROR_INVALID_VALUE``, because the driver received ``-1`` as the NUMA
310+
node. :class:`VirtualMemoryResourceOptions` has a new ``numa_id`` field that
311+
names the node. It is required for ``"host_numa"`` and rejected for every
312+
other location type; ``Device.properties.host_numa_id`` gives the node
313+
closest to a device. ``location_type="host_numa_current"`` now raises
314+
``ValueError`` at construction, because the driver does not accept it for
315+
physical allocations.
316+
(`#2694 <https://github.com/NVIDIA/cuda-python/issues/2694>`__)

‎cuda_core/tests/memory_ipc/test_vmm_ipc.py‎

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -193,9 +193,8 @@ def _on_queue_feeder_error(e, _obj):
193193
pytest.param({"handle_type": "fabric"}, False, id="fabric"),
194194
pytest.param({"handle_type": "win32_kmt"}, False, id="win32_kmt"),
195195
pytest.param({"location_type": "host", "handle_type": None}, False, id="host"),
196-
pytest.param({"location_type": "host_numa", "handle_type": "posix_fd"}, False, id="host_numa-posix_fd"),
197196
pytest.param(
198-
{"location_type": "host_numa_current", "handle_type": "posix_fd"}, False, id="host_numa_current-posix_fd"
197+
{"location_type": "host_numa", "numa_id": 0, "handle_type": "posix_fd"}, False, id="host_numa-posix_fd"
199198
),
200199
],
201200
)

‎cuda_core/tests/test_memory.py‎

Lines changed: 71 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3117,32 +3117,98 @@ def test_vmm_options_handle_type_win32_raises():
31173117

31183118

31193119
@pytest.mark.agent_authored(model="claude-fable-5-1")
3120-
@pytest.mark.parametrize("location_type", ["host", "host_numa", "host_numa_current"])
3120+
@pytest.mark.parametrize("location_type", ["host", "host_numa"])
31213121
def test_vmm_host_location_types_report_host_accessible(init_cuda, location_type):
31223122
"""Every host-backed location type reports is_host_accessible; only "host" needs handle_type=None.
31233123
3124-
__init__ classifies "host", "host_numa" and "host_numa_current" alike when
3124+
__init__ classifies "host" and "host_numa" alike when
31253125
deciding the resource is not bound to a device, so is_host_accessible must
31263126
agree; otherwise a NUMA-located resource claims to be neither host- nor
31273127
device-accessible. The driver rejects an exportable handle type for HOST
31283128
memory only, so the default handle type is rejected at construction for
3129-
"host" and accepted for the NUMA location types.
3129+
"host" and accepted for "host_numa".
31303130
"""
31313131
device = Device()
31323132
device.set_current()
3133+
numa = {"numa_id": 0} if location_type == "host_numa" else {}
31333134
if location_type == "host":
31343135
with pytest.raises(ValueError, match="handle_type=None"):
31353136
VirtualMemoryResource(device, config=VirtualMemoryResourceOptions(location_type=location_type))
31363137
else:
3137-
VirtualMemoryResource(device, config=VirtualMemoryResourceOptions(location_type=location_type))
3138+
VirtualMemoryResource(device, config=VirtualMemoryResourceOptions(location_type=location_type, **numa))
31383139
mr = VirtualMemoryResource(
3139-
device, config=VirtualMemoryResourceOptions(location_type=location_type, handle_type=None)
3140+
device, config=VirtualMemoryResourceOptions(location_type=location_type, handle_type=None, **numa)
31403141
)
31413142
assert mr.device is None
31423143
assert mr.is_host_accessible is True
31433144
assert mr.device_id == -1
31443145

31453146

3147+
@pytest.mark.thread_unsafe(reason="records process-global warnings")
3148+
@pytest.mark.agent_authored(model="claude-opus-5-5")
3149+
def test_vmm_host_numa_allocates_and_copies(init_cuda):
3150+
"""A "host_numa" resource allocates on its NUMA node, and the host can use the memory.
3151+
3152+
The driver rejects CU_MEM_LOCATION_TYPE_HOST_NUMA with location id -1, so
3153+
the allocation works only if numa_id reaches the allocation properties
3154+
(#2694). The resource grants access to its own location only, so the data
3155+
goes through the host. A per-call config cannot move the buffer to another
3156+
node.
3157+
"""
3158+
device = Device()
3159+
device.set_current()
3160+
props = device.properties
3161+
if not props.host_numa_virtual_memory_management_supported:
3162+
pytest.skip("Host NUMA virtual memory management is not supported on this device")
3163+
if props.host_numa_id < 0:
3164+
pytest.skip("The system reports no host NUMA node")
3165+
mr = VirtualMemoryResource(
3166+
device,
3167+
config=VirtualMemoryResourceOptions(location_type="host_numa", numa_id=props.host_numa_id, handle_type=None),
3168+
)
3169+
with assert_no_cuda_warning():
3170+
buf = mr.allocate(4096)
3171+
try:
3172+
assert buf.is_host_accessible
3173+
ctypes.memset(int(buf.handle), 0xAB, buf.size)
3174+
assert ctypes.string_at(int(buf.handle), buf.size) == b"\xab" * buf.size
3175+
other_node = VirtualMemoryResourceOptions(
3176+
location_type="host_numa", numa_id=props.host_numa_id + 1, handle_type=None
3177+
)
3178+
with pytest.raises(ValueError, match="config.numa_id"):
3179+
mr.modify_allocation(buf, 2 * buf.size, other_node)
3180+
finally:
3181+
buf.close()
3182+
3183+
3184+
@pytest.mark.agent_authored(model="claude-opus-5-5")
3185+
def test_vmm_host_numa_current_rejected(init_cuda):
3186+
"""The driver does not accept "host_numa_current" for physical allocations, so construction rejects it."""
3187+
device = Device()
3188+
device.set_current()
3189+
with pytest.raises(ValueError, match="does not accept it for physical allocations"):
3190+
VirtualMemoryResource(
3191+
device, config=VirtualMemoryResourceOptions(location_type="host_numa_current", handle_type=None)
3192+
)
3193+
3194+
3195+
@pytest.mark.agent_authored(model="claude-opus-5-5")
3196+
@pytest.mark.parametrize(
3197+
("options", "match"),
3198+
[
3199+
pytest.param({"location_type": "host_numa"}, "requires numa_id", id="host_numa-missing"),
3200+
pytest.param({"location_type": "host_numa", "numa_id": -1}, "numa_id must be >= 0", id="host_numa-negative"),
3201+
pytest.param({"location_type": "host", "numa_id": 0}, "only valid with", id="host-misplaced"),
3202+
pytest.param({"location_type": "device", "numa_id": 0}, "only valid with", id="device-misplaced"),
3203+
],
3204+
)
3205+
def test_vmm_numa_id_validation(init_cuda, options, match):
3206+
"""numa_id is required for "host_numa", must be non-negative, and is rejected for other location types."""
3207+
device = _vmm_device_or_skip()
3208+
with pytest.raises(ValueError, match=match):
3209+
VirtualMemoryResource(device, config=VirtualMemoryResourceOptions(handle_type=None, **options))
3210+
3211+
31463212
def test_device_memory_resource_peer_accessible_by_non_owned(mempool_device):
31473213
"""peer_accessible_by on a non-owned (default) DMR queries the driver live."""
31483214
dev = mempool_device

0 commit comments

Comments
 (0)