Skip to content

Commit ee92548

Browse files
artulabayildiri
andauthored
Simplify driver APIs and improve nvl72 fabric network detection (#540)
Co-authored-by: ayildiri <ayildiri@amd.com>
1 parent 1a8356d commit ee92548

11 files changed

Lines changed: 316 additions & 375 deletions

File tree

iris/drivers/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,9 @@
1010
from dataclasses import dataclass
1111
from typing import Optional
1212

13-
from iris.drivers.base import BaseDriver
13+
from iris.drivers.base import BaseDriver, CleanupTarget, ExportableMemory, MappingPlacement
1414

15-
__all__ = ["DriverStack"]
15+
__all__ = ["DriverStack", "CleanupTarget", "ExportableMemory", "MappingPlacement"]
1616

1717

1818
@dataclass

iris/drivers/base.py

Lines changed: 43 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@
1616
__all__ = [
1717
"PeerMapping",
1818
"LocalAllocation",
19+
"CleanupTarget",
20+
"ExportableMemory",
21+
"MappingPlacement",
1922
"BaseDriver",
2023
"DriverError",
2124
"DriverNotSupported",
@@ -43,6 +46,40 @@ class LocalAllocation:
4346
_va_owned: bool = True
4447

4548

49+
CleanupTarget = LocalAllocation | PeerMapping
50+
51+
52+
@dataclass(frozen=True)
53+
class ExportableMemory:
54+
"""A local memory range that can be exported to peers."""
55+
56+
va: int
57+
size: int
58+
allocation: Optional[LocalAllocation] = None
59+
60+
61+
@dataclass(frozen=True)
62+
class MappingPlacement:
63+
"""Caller-reserved virtual address placement for a VMM mapping.
64+
65+
If arena_base is set, the mapping belongs to a larger reserved VA arena.
66+
Drivers that need cumulative access programming can use that arena context
67+
without exposing access-range details in the public API.
68+
"""
69+
70+
va: int
71+
arena_base: Optional[int] = None
72+
73+
def access_range(self, mapped_size: int, cumulative: bool = False) -> tuple[int, int]:
74+
if not cumulative or self.arena_base is None:
75+
return int(self.va), int(mapped_size)
76+
77+
offset = int(self.va) - int(self.arena_base)
78+
if offset < 0:
79+
raise ValueError(f"placement va {self.va} is below arena base {self.arena_base}")
80+
return int(self.arena_base), offset + int(mapped_size)
81+
82+
4683
class DriverError(RuntimeError):
4784
"""Base exception for driver operations."""
4885

@@ -62,37 +99,27 @@ def initialize(self, device_ordinal: int) -> None:
6299
def allocate_exportable(
63100
self,
64101
size: int,
65-
va: Optional[int] = None,
66-
*,
67-
access_va: Optional[int] = None,
68-
access_size: Optional[int] = None,
102+
placement: Optional[MappingPlacement] = None,
69103
) -> LocalAllocation:
70104
"""Allocate exportable memory, optionally mapping it at a caller-reserved VA."""
71105

72106
@abstractmethod
73-
def export_handle(self, allocation: LocalAllocation) -> bytes:
74-
"""Export a transport-specific handle for a local allocation."""
107+
def export_handle(self, memory: ExportableMemory) -> bytes:
108+
"""Export a transport-specific handle for a local memory range."""
75109

76110
@abstractmethod
77111
def import_and_map(
78112
self,
79113
peer_rank: int,
80114
handle_bytes: bytes,
81115
size: int,
82-
va: Optional[int] = None,
83-
*,
84-
access_va: Optional[int] = None,
85-
access_size: Optional[int] = None,
116+
placement: Optional[MappingPlacement] = None,
86117
) -> PeerMapping:
87118
"""Import a peer handle and map it into the local virtual address space."""
88119

89120
@abstractmethod
90-
def cleanup_import(self, mapping: PeerMapping) -> None:
91-
"""Release a mapped peer allocation."""
92-
93-
@abstractmethod
94-
def cleanup_local(self, allocation: LocalAllocation) -> None:
95-
"""Release a locally-exported allocation."""
121+
def cleanup(self, target: CleanupTarget) -> None:
122+
"""Release a local allocation or imported peer mapping."""
96123

97124
@abstractmethod
98125
def get_minimum_granularity(self) -> int:
@@ -109,7 +136,3 @@ def free_va(self, va: int, size: int) -> None:
109136
def get_address_range(self, ptr: int) -> tuple[int, int]:
110137
"""Return the base VA and size of the allocation containing ptr."""
111138
raise DriverNotSupported(f"{type(self).__name__} does not support get_address_range")
112-
113-
def export_pointer_handle(self, ptr: int, size: int) -> bytes:
114-
"""Export a peer handle for an arbitrary device pointer."""
115-
raise DriverNotSupported(f"{type(self).__name__} does not support export_pointer_handle")

iris/drivers/fabric/amd.py

Lines changed: 6 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@
1212
from iris.drivers.base import (
1313
BaseDriver,
1414
DriverNotSupported,
15+
ExportableMemory,
1516
LocalAllocation,
17+
MappingPlacement,
1618
PeerMapping,
1719
)
1820

@@ -30,32 +32,23 @@ def initialize(self, device_ordinal: int) -> None:
3032
def allocate_exportable(
3133
self,
3234
size: int,
33-
va: Optional[int] = None,
34-
*,
35-
access_va: Optional[int] = None,
36-
access_size: Optional[int] = None,
35+
placement: Optional[MappingPlacement] = None,
3736
) -> LocalAllocation:
3837
raise DriverNotSupported(_NOT_IMPLEMENTED_MESSAGE)
3938

40-
def export_handle(self, allocation: LocalAllocation) -> bytes:
39+
def export_handle(self, memory: ExportableMemory) -> bytes:
4140
raise DriverNotSupported(_NOT_IMPLEMENTED_MESSAGE)
4241

4342
def import_and_map(
4443
self,
4544
peer_rank: int,
4645
handle_bytes: bytes,
4746
size: int,
48-
va: Optional[int] = None,
49-
*,
50-
access_va: Optional[int] = None,
51-
access_size: Optional[int] = None,
47+
placement: Optional[MappingPlacement] = None,
5248
) -> PeerMapping:
5349
raise DriverNotSupported(_NOT_IMPLEMENTED_MESSAGE)
5450

55-
def cleanup_import(self, mapping: PeerMapping) -> None:
56-
raise DriverNotSupported(_NOT_IMPLEMENTED_MESSAGE)
57-
58-
def cleanup_local(self, allocation: LocalAllocation) -> None:
51+
def cleanup(self, target: LocalAllocation | PeerMapping) -> None:
5952
raise DriverNotSupported(_NOT_IMPLEMENTED_MESSAGE)
6053

6154
def get_minimum_granularity(self) -> int:

iris/drivers/fabric/nvidia.py

Lines changed: 29 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,9 @@
1818
BaseDriver,
1919
DriverError,
2020
DriverNotSupported,
21+
ExportableMemory,
2122
LocalAllocation,
23+
MappingPlacement,
2224
PeerMapping,
2325
)
2426
from iris.host.distributed.topology import InterconnectLevel
@@ -339,20 +341,15 @@ def _check_initialized(self) -> None:
339341
def allocate_exportable(
340342
self,
341343
size: int,
342-
va: Optional[int] = None,
343-
*,
344-
access_va: Optional[int] = None,
345-
access_size: Optional[int] = None,
344+
placement: Optional[MappingPlacement] = None,
346345
) -> LocalAllocation:
347346
self._check_initialized()
348-
if (access_va is None) != (access_size is None):
349-
raise CudaFabricError("access_va and access_size must be provided together")
350347
props = self._make_alloc_props()
351348
granularity = self._get_granularity()
352349
alloc_size = _round_up(size, granularity)
353350

354-
reserved_va = va is None
355-
mapped_va = int(va) if va is not None else 0
351+
reserved_va = placement is None
352+
mapped_va = int(placement.va) if placement is not None else 0
356353
handle = ctypes.c_uint64()
357354
mapped = False
358355

@@ -373,10 +370,10 @@ def allocate_exportable(
373370
"cuMemMap",
374371
)
375372
mapped = True
376-
self._mem_set_access(
377-
int(access_va) if access_va is not None else mapped_va,
378-
int(access_size) if access_size is not None else alloc_size,
373+
access_base, access_bytes = (
374+
placement.access_range(alloc_size) if placement is not None else (mapped_va, alloc_size)
379375
)
376+
self._mem_set_access(access_base, access_bytes)
380377
return LocalAllocation(
381378
va=mapped_va,
382379
size=alloc_size,
@@ -404,13 +401,16 @@ def allocate_exportable(
404401
pass
405402
raise
406403

407-
def export_handle(self, allocation: LocalAllocation) -> bytes:
404+
def export_handle(self, memory: ExportableMemory) -> bytes:
408405
self._check_initialized()
406+
if memory.allocation is None:
407+
raise CudaFabricNotSupported("NVIDIA fabric driver can only export driver-created VMM allocations")
408+
409409
raw = (ctypes.c_ubyte * FABRIC_HANDLE_BYTES)()
410410
_cuda_try(
411411
_cuda_driver.cuMemExportToShareableHandle(
412412
ctypes.byref(raw),
413-
int(allocation.handle),
413+
int(memory.allocation.handle),
414414
_CU_MEM_HANDLE_TYPE_FABRIC,
415415
0,
416416
),
@@ -437,19 +437,14 @@ def import_and_map(
437437
peer_rank: int,
438438
handle_bytes: bytes,
439439
size: int,
440-
va: Optional[int] = None,
441-
*,
442-
access_va: Optional[int] = None,
443-
access_size: Optional[int] = None,
440+
placement: Optional[MappingPlacement] = None,
444441
) -> PeerMapping:
445442
self._check_initialized()
446-
if (access_va is None) != (access_size is None):
447-
raise CudaFabricError("access_va and access_size must be provided together")
448443
imported_handle = self._import_handle(handle_bytes)
449444

450445
granularity = self._get_granularity()
451-
va_owned = va is None
452-
mapped_va = int(va) if va is not None else 0
446+
va_owned = placement is None
447+
mapped_va = int(placement.va) if placement is not None else 0
453448
mapped = False
454449
try:
455450
if va_owned:
@@ -464,10 +459,8 @@ def import_and_map(
464459
"cuMemMap",
465460
)
466461
mapped = True
467-
self._mem_set_access(
468-
int(access_va) if access_va is not None else mapped_va,
469-
int(access_size) if access_size is not None else size,
470-
)
462+
access_base, access_bytes = placement.access_range(size) if placement is not None else (mapped_va, size)
463+
self._mem_set_access(access_base, access_bytes)
471464
except Exception:
472465
if mapped:
473466
try:
@@ -497,7 +490,16 @@ def import_and_map(
497490
_driver_handle=(tag, imported_handle),
498491
)
499492

500-
def cleanup_import(self, mapping: PeerMapping) -> None:
493+
def cleanup(self, target: LocalAllocation | PeerMapping) -> None:
494+
if isinstance(target, LocalAllocation):
495+
self._cleanup_local(target)
496+
return
497+
if isinstance(target, PeerMapping):
498+
self._cleanup_import(target)
499+
return
500+
raise CudaFabricError(f"Unsupported cleanup target: {type(target).__name__}")
501+
502+
def _cleanup_import(self, mapping: PeerMapping) -> None:
501503
self._check_initialized()
502504
if isinstance(mapping._driver_handle, tuple) and len(mapping._driver_handle) == 2:
503505
tag, imported_handle = mapping._driver_handle
@@ -533,7 +535,7 @@ def cleanup_import(self, mapping: PeerMapping) -> None:
533535
)
534536
_run_cleanup_steps(*steps)
535537

536-
def cleanup_local(self, allocation: LocalAllocation) -> None:
538+
def _cleanup_local(self, allocation: LocalAllocation) -> None:
537539
self._check_initialized()
538540
steps = [
539541
(

0 commit comments

Comments
 (0)