Coverage for cuda/core/_memory/_copy_enums.py: 42.59%

54 statements  

« prev     ^ index     » next       coverage.py v7.16.2, created at 2026-10-16 01:23 +0000

1# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. 

2# 

3# SPDX-License-Identifier: Apache-2.0 

4 

5from __future__ import annotations 

6 

7import dataclasses 

8from collections.abc import Sequence 

9from enum import StrEnum 

10 

11from cuda.core._device import Device 

12from cuda.core._host import Host 

13from cuda.core._utils.cuda_utils import driver 

14 

15__all__ = ["CopyOptions", "MemcpyOverlapMode", "MemcpySrcAccessOrder"] 

16 

17 

18class MemcpySrcAccessOrder(StrEnum): 

19 """Source access order hint for batched memcpy operations. 

20 

21 Maps to ``CUmemcpySrcAccessOrder``. 

22 

23 ``STREAM`` 

24 Source reads follow stream order. Earlier stream work may still be 

25 accessing the source when the copy is enqueued. 

26 ``DURING_API_CALL`` 

27 The driver may read the source out of stream order, but all reads 

28 are complete before :func:`copy_batch` returns. No earlier stream 

29 work may be accessing the source at the time of the call. 

30 ``ANY`` 

31 The driver may read the source after the call returns. The caller 

32 must keep the source unchanged until the copy completes in stream 

33 order. No earlier stream work may be accessing the source. 

34 """ 

35 

36 STREAM = "stream" 

37 DURING_API_CALL = "during_api_call" 

38 ANY = "any" 

39 

40 

41class MemcpyOverlapMode(StrEnum): 

42 """Overlap mode hint for batched memcpy operations. 

43 

44 Maps to ``CUmemcpyFlags``. 

45 

46 ``DEFAULT`` 

47 No overlap preference; the driver uses its default scheduling. 

48 ``PREFER_OVERLAP_WITH_COMPUTE`` 

49 Hint that the copy should preferably overlap with concurrent 

50 compute work. This is advisory and may be ignored depending on 

51 the platform and copy parameters. 

52 """ 

53 

54 DEFAULT = "default" 

55 PREFER_OVERLAP_WITH_COMPUTE = "prefer_overlap_with_compute" 

56 

57 

58@dataclasses.dataclass(frozen=True) 

59class CopyOptions: 

60 """Attribute bundle for a single copy within a batched memcpy. 

61 

62 Parameters 

63 ---------- 

64 src_access_order : :class:`MemcpySrcAccessOrder` or str 

65 Hint describing how the source will be accessed. 

66 Default is ``"stream"`` (stream-ordered access). 

67 src_location_hint : :class:`cuda.core.Device` | :class:`cuda.core.Host` | None 

68 Hint for the source memory location. Honored only for managed 

69 memory on devices with concurrent managed access and for 

70 system-allocated pageable memory on devices with pageable memory 

71 access; ignored for all other memory types. Does not prefetch 

72 memory and does not set persistent memory advice. 

73 ``None`` means no hint. 

74 dst_location_hint : :class:`cuda.core.Device` | :class:`cuda.core.Host` | None 

75 Hint for the destination memory location. Same semantics and 

76 restrictions as ``src_location_hint``. ``None`` means no hint. 

77 overlap_mode : :class:`MemcpyOverlapMode` or str 

78 Hint requesting that the copy overlap with concurrent compute work. 

79 This is advisory; it has an effect only on devices that support it. 

80 Default is ``"default"``. 

81 """ 

82 

83 src_access_order: MemcpySrcAccessOrder | str = "stream" 

84 src_location_hint: Device | Host | None = None 

85 dst_location_hint: Device | Host | None = None 

86 overlap_mode: MemcpyOverlapMode | str = "default" 

87 

88 def __post_init__(self): 

89 # Frozen, unlike the other *Options dataclasses in cuda.core, because 

90 # the batched-API contract agreed in NVIDIA/cuda-python#1775 specifies 

91 # immutable per-call options: 

92 # https://github.com/NVIDIA/cuda-python/pull/1775#issuecomment-4355502334 

93 # 

94 # Normalizing str -> StrEnum therefore has to go through 

95 # object.__setattr__; a plain assignment would raise 

96 # FrozenInstanceError. Done here rather than at use so that a typo 

97 # fails at construction and the field always holds the enum. 

98 if not isinstance(self.src_access_order, MemcpySrcAccessOrder): 1abcdefghijknlmopqrstCDRzwxuEySvFGHABTIJUKVLMNOPQ

99 try: 1abcdefghijklmqCDRzuEvABL

100 object.__setattr__( 1abcdefghijklmqCDRzuEvABL

101 self, 

102 "src_access_order", 

103 MemcpySrcAccessOrder(self.src_access_order), 

104 ) 

105 except (ValueError, TypeError) as exc: 1R

106 raise ValueError(f"invalid src_access_order: {self.src_access_order!r}") from exc 1R

107 if not isinstance(self.overlap_mode, MemcpyOverlapMode): 1abcdefghijknlmopqrstCDzwxuEySvFGHABTIJUKVLMNOPQ

108 try: 1abcdefghijknlmoprstCDzwxuEySvFGHABTIJUKVMNOPQ

109 object.__setattr__( 1abcdefghijknlmoprstCDzwxuEySvFGHABTIJUKVMNOPQ

110 self, 

111 "overlap_mode", 

112 MemcpyOverlapMode(self.overlap_mode), 

113 ) 

114 except (ValueError, TypeError) as exc: 1z

115 raise ValueError(f"invalid overlap_mode: {self.overlap_mode!r}") from exc 1z

116 

117 def _to_driver_enum(self) -> int: 

118 """Return the driver CUmemcpySrcAccessOrder value.""" 

119 return _SRC_ACCESS_ORDER_TO_DRIVER[MemcpySrcAccessOrder(self.src_access_order)] 1abcdefghijknlmopqrstFGHABIJKLMNOPQ

120 

121 def _to_driver_flags(self) -> int: 

122 """Return the driver CUmemcpyFlags value.""" 

123 return _OVERLAP_MODE_TO_DRIVER[MemcpyOverlapMode(self.overlap_mode)] 1abcdefghijknlmopqrstFGHABIJKLMNOPQ

124 

125 

126# CUDA 12.8 added CUmemcpySrcAccessOrder and CUmemcpyFlags. Every 

127# cuda-bindings that cuda.core accepts has them. Keyed by ``str`` (StrEnum 

128# members are ``str``) and valued as ``int``. 

129_src_order = driver.CUmemcpySrcAccessOrder 

130_flags = driver.CUmemcpyFlags 

131_SRC_ACCESS_ORDER_TO_DRIVER: dict[str, int] = { 

132 MemcpySrcAccessOrder.STREAM: int(_src_order.CU_MEMCPY_SRC_ACCESS_ORDER_STREAM), 

133 MemcpySrcAccessOrder.DURING_API_CALL: int(_src_order.CU_MEMCPY_SRC_ACCESS_ORDER_DURING_API_CALL), 

134 MemcpySrcAccessOrder.ANY: int(_src_order.CU_MEMCPY_SRC_ACCESS_ORDER_ANY), 

135} 

136_OVERLAP_MODE_TO_DRIVER: dict[str, int] = { 

137 MemcpyOverlapMode.DEFAULT: int(_flags.CU_MEMCPY_FLAG_DEFAULT), 

138 MemcpyOverlapMode.PREFER_OVERLAP_WITH_COMPUTE: int(_flags.CU_MEMCPY_FLAG_PREFER_OVERLAP_WITH_COMPUTE), 

139} 

140del _src_order, _flags 

141 

142 

143def _reject_unsupported_during_api_call( 

144 src_access_order: MemcpySrcAccessOrder, requirement: str, *, index: int | None = None 

145) -> None: 

146 """Raise if ``src_access_order`` is DURING_API_CALL but the native attributes 

147 path (``cuMemcpyWithAttributesAsync`` / ``cuMemcpyBatchAsync``) is unavailable. 

148 

149 STREAM and ANY never promise access sooner than stream order, so a plain 

150 ``cuMemcpyAsync`` fallback satisfies them; DURING_API_CALL specifically 

151 promises all source reads complete before the call returns, which 

152 ``cuMemcpyAsync`` cannot provide (it reads the source in stream order 

153 only). Silently downgrading that guarantee would let a caller reuse or 

154 overwrite the source buffer before the real, stream-ordered read 

155 happens: a silent data race, not a missed optimization. ``requirement`` 

156 names what the native path needs and why it is unavailable here; 

157 ``index`` identifies the offending copy within a batch. 

158 

159 Internal, but deliberately importable: shared between the per-buffer and 

160 batched fallback paths so both raise identically, and directly testable 

161 without needing an actual old driver/bindings install. 

162 """ 

163 if src_access_order != MemcpySrcAccessOrder.DURING_API_CALL: 1WXYZ0

164 return 1Z0

165 where = f" at index {index}" if index is not None else "" 1WXY

166 raise RuntimeError( 1WXY

167 f"src_access_order=DURING_API_CALL{where} requires {requirement}. A " 

168 "plain cuMemcpyAsync fallback reads the source in stream order only, " 

169 "which would silently violate the guarantee that all source reads " 

170 "complete before the call returns, letting the caller reuse the " 

171 "source buffer before the real (stream-ordered) read happens. Use " 

172 "src_access_order=STREAM or ANY, or omit options, if that works for " 

173 "your use case." 

174 ) 

175 

176 

177def _attr_run_starts(attrs: Sequence[CopyOptions]) -> list[int]: 

178 """Return the start index of each maximal run of equal attributes. 

179 

180 This mirrors the ``attrsIdxs`` indirection that ``cuMemcpyBatchAsync`` 

181 expects: ``attrs[k]`` applies to the copies in 

182 ``[starts[k], starts[k + 1])``. Collapsing equal neighbours means a 

183 broadcast attribute is passed to the driver once (``numAttrs == 1``) 

184 rather than repeated per copy. 

185 """ 

186 starts: list[int] = [] 1abcdefghijknlmopqrstwxuyv

187 prev: CopyOptions | None = None 1abcdefghijknlmopqrstwxuyv

188 for i, attr in enumerate(attrs): 1abcdefghijknlmopqrstwxuyv

189 if i == 0 or attr != prev: 1abcdefghijknlmopqrstwxuyv

190 starts.append(i) 1abcdefghijknlmopqrstwxuyv

191 prev = attr 1abcdefghijknlmopqrstwxuyv

192 return starts 1abcdefghijknlmopqrstwxuyv