nvmath-python 0.5.0__cp313-cp313-win_amd64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- nvmath/__init__.pxd +0 -0
- nvmath/__init__.py +37 -0
- nvmath/_internal/__init__.py +0 -0
- nvmath/_internal/layout.py +58 -0
- nvmath/_utils.py +363 -0
- nvmath/bindings/__init__.pxd +0 -0
- nvmath/bindings/__init__.py +44 -0
- nvmath/bindings/_internal/__init__.pxd +0 -0
- nvmath/bindings/_internal/__init__.py +0 -0
- nvmath/bindings/_internal/cublas.cp313-win_amd64.pyd +0 -0
- nvmath/bindings/_internal/cublas.pxd +518 -0
- nvmath/bindings/_internal/cublasLt.cp313-win_amd64.pyd +0 -0
- nvmath/bindings/_internal/cublasLt.pxd +54 -0
- nvmath/bindings/_internal/cudss.cp313-win_amd64.pyd +0 -0
- nvmath/bindings/_internal/cudss.pxd +44 -0
- nvmath/bindings/_internal/cufft.cp313-win_amd64.pyd +0 -0
- nvmath/bindings/_internal/cufft.pxd +69 -0
- nvmath/bindings/_internal/cufftMp.pxd +77 -0
- nvmath/bindings/_internal/curand.cp313-win_amd64.pyd +0 -0
- nvmath/bindings/_internal/curand.pxd +42 -0
- nvmath/bindings/_internal/cusolver.cp313-win_amd64.pyd +0 -0
- nvmath/bindings/_internal/cusolver.pxd +15 -0
- nvmath/bindings/_internal/cusolverDn.cp313-win_amd64.pyd +0 -0
- nvmath/bindings/_internal/cusolverDn.pxd +386 -0
- nvmath/bindings/_internal/cusparse.cp313-win_amd64.pyd +0 -0
- nvmath/bindings/_internal/cusparse.pxd +270 -0
- nvmath/bindings/_internal/mathdx.cp313-win_amd64.pyd +0 -0
- nvmath/bindings/_internal/mathdx.pxd +79 -0
- nvmath/bindings/_internal/nvshmem.pxd +29 -0
- nvmath/bindings/_internal/utils.cp313-win_amd64.pyd +0 -0
- nvmath/bindings/_internal/utils.pxd +186 -0
- nvmath/bindings/cublas.cp313-win_amd64.pyd +0 -0
- nvmath/bindings/cublas.pxd +545 -0
- nvmath/bindings/cublas.pyi +18 -0
- nvmath/bindings/cublasLt.cp313-win_amd64.pyd +0 -0
- nvmath/bindings/cublasLt.pxd +99 -0
- nvmath/bindings/cudss.cp313-win_amd64.pyd +0 -0
- nvmath/bindings/cudss.pxd +83 -0
- nvmath/bindings/cudss.pyi +249 -0
- nvmath/bindings/cufft.cp313-win_amd64.pyd +0 -0
- nvmath/bindings/cufft.pxd +114 -0
- nvmath/bindings/cufftMp.pxd +124 -0
- nvmath/bindings/curand.cp313-win_amd64.pyd +0 -0
- nvmath/bindings/curand.pxd +71 -0
- nvmath/bindings/cusolver.cp313-win_amd64.pyd +0 -0
- nvmath/bindings/cusolver.pxd +60 -0
- nvmath/bindings/cusolverDn.cp313-win_amd64.pyd +0 -0
- nvmath/bindings/cusolverDn.pxd +410 -0
- nvmath/bindings/cusparse.cp313-win_amd64.pyd +0 -0
- nvmath/bindings/cusparse.pxd +329 -0
- nvmath/bindings/cycublas.cp313-win_amd64.pyd +0 -0
- nvmath/bindings/cycublas.pxd +657 -0
- nvmath/bindings/cycublasLt.cp313-win_amd64.pyd +0 -0
- nvmath/bindings/cycublasLt.pxd +1010 -0
- nvmath/bindings/cycudss.cp313-win_amd64.pyd +0 -0
- nvmath/bindings/cycudss.pxd +191 -0
- nvmath/bindings/cycufft.cp313-win_amd64.pyd +0 -0
- nvmath/bindings/cycufft.pxd +320 -0
- nvmath/bindings/cycufftMp.pxd +333 -0
- nvmath/bindings/cycurand.cp313-win_amd64.pyd +0 -0
- nvmath/bindings/cycurand.pxd +146 -0
- nvmath/bindings/cycusolver.cp313-win_amd64.pyd +0 -0
- nvmath/bindings/cycusolver.pxd +143 -0
- nvmath/bindings/cycusolverDn.cp313-win_amd64.pyd +0 -0
- nvmath/bindings/cycusolverDn.pxd +414 -0
- nvmath/bindings/cycusparse.cp313-win_amd64.pyd +0 -0
- nvmath/bindings/cycusparse.pxd +476 -0
- nvmath/bindings/cymathdx.cp313-win_amd64.pyd +0 -0
- nvmath/bindings/cymathdx.pxd +363 -0
- nvmath/bindings/cynvshmem.pxd +126 -0
- nvmath/bindings/mathdx.cp313-win_amd64.pyd +0 -0
- nvmath/bindings/mathdx.pxd +131 -0
- nvmath/bindings/mathdx.pyi +686 -0
- nvmath/bindings/nvpl/__init__.pxd +0 -0
- nvmath/bindings/nvpl/__init__.py +11 -0
- nvmath/bindings/nvpl/_internal/__init__.pxd +0 -0
- nvmath/bindings/nvpl/_internal/__init__.py +0 -0
- nvmath/bindings/nvpl/_internal/fft.pxd +36 -0
- nvmath/bindings/nvpl/cyfft.pxd +87 -0
- nvmath/bindings/nvpl/fft.pxd +100 -0
- nvmath/bindings/nvshmem.pxd +54 -0
- nvmath/device/__init__.py +23 -0
- nvmath/device/_deprecated.py +33 -0
- nvmath/device/caching.py +51 -0
- nvmath/device/common.py +131 -0
- nvmath/device/common_backend.py +125 -0
- nvmath/device/common_cuda.py +156 -0
- nvmath/device/common_mathdx.py +97 -0
- nvmath/device/common_numba.py +159 -0
- nvmath/device/common_opaque_tensor.py +128 -0
- nvmath/device/cublasdx.py +1085 -0
- nvmath/device/cublasdx_backend.py +552 -0
- nvmath/device/cublasdx_numba.py +528 -0
- nvmath/device/cufftdx.py +551 -0
- nvmath/device/cufftdx_backend.py +194 -0
- nvmath/device/cufftdx_numba.py +123 -0
- nvmath/device/curand_kernel.py +9147 -0
- nvmath/device/nvrtc.py +95 -0
- nvmath/device/patch.py +35 -0
- nvmath/device/random.py +417 -0
- nvmath/device/random_helpers.py +23 -0
- nvmath/device/random_states.py +189 -0
- nvmath/device/types.py +11 -0
- nvmath/device/vector_types_numba.py +203 -0
- nvmath/distributed/__init__.py +117 -0
- nvmath/distributed/_internal/__init__.py +0 -0
- nvmath/distributed/_internal/nvshmem.py +243 -0
- nvmath/distributed/_internal/tensor_ifc_cupy.py +111 -0
- nvmath/distributed/_internal/tensor_ifc_numpy.py +53 -0
- nvmath/distributed/_internal/tensor_ifc_torch.py +108 -0
- nvmath/distributed/_internal/tensor_wrapper.py +59 -0
- nvmath/distributed/_utils.py +152 -0
- nvmath/distributed/fft/__init__.py +6 -0
- nvmath/distributed/fft/_configuration.py +86 -0
- nvmath/distributed/fft/fft.py +1737 -0
- nvmath/distributed/reshape/__init__.py +6 -0
- nvmath/distributed/reshape/_configuration.py +39 -0
- nvmath/distributed/reshape/reshape.py +1249 -0
- nvmath/fft/__init__.py +7 -0
- nvmath/fft/_configuration.py +208 -0
- nvmath/fft/_exec_utils.py +116 -0
- nvmath/fft/_helpers.py +260 -0
- nvmath/fft/fft.py +2346 -0
- nvmath/internal/__init__.py +5 -0
- nvmath/internal/enum_utils.py +142 -0
- nvmath/internal/formatters.py +87 -0
- nvmath/internal/mem_limit.py +51 -0
- nvmath/internal/package_ifc.py +119 -0
- nvmath/internal/package_ifc_cuda.py +43 -0
- nvmath/internal/package_ifc_cupy.py +49 -0
- nvmath/internal/package_ifc_torch.py +31 -0
- nvmath/internal/package_wrapper.py +14 -0
- nvmath/internal/tensor_ifc.py +143 -0
- nvmath/internal/tensor_ifc_cupy.py +185 -0
- nvmath/internal/tensor_ifc_numpy.py +157 -0
- nvmath/internal/tensor_ifc_torch.py +115 -0
- nvmath/internal/tensor_wrapper.py +160 -0
- nvmath/internal/typemaps.py +154 -0
- nvmath/internal/utils.py +634 -0
- nvmath/linalg/__init__.py +11 -0
- nvmath/linalg/_internal/__init__.py +3 -0
- nvmath/linalg/_internal/algo_cap_ifc.py +86 -0
- nvmath/linalg/_internal/algo_config_ifc.py +87 -0
- nvmath/linalg/_internal/enum_to_tuples.py +64 -0
- nvmath/linalg/_internal/epilog_protocol.py +738 -0
- nvmath/linalg/_internal/matmul_desc_ifc.py +72 -0
- nvmath/linalg/_internal/matmul_pref_ifc.py +65 -0
- nvmath/linalg/_internal/matrix_layout_ifc.py +59 -0
- nvmath/linalg/_internal/typemaps.py +135 -0
- nvmath/linalg/_internal/utils.py +96 -0
- nvmath/linalg/advanced/__init__.py +8 -0
- nvmath/linalg/advanced/_algorithmmod.py +142 -0
- nvmath/linalg/advanced/_configuration.py +320 -0
- nvmath/linalg/advanced/helpers/__init__.py +5 -0
- nvmath/linalg/advanced/helpers/matmul.py +173 -0
- nvmath/linalg/advanced/matmulmod.py +2855 -0
- nvmath/memory.py +266 -0
- nvmath/sparse/__init__.py +18 -0
- nvmath/sparse/_internal/__init__.py +21 -0
- nvmath/sparse/_internal/common_utils.py +81 -0
- nvmath/sparse/_internal/cudss_config_ifc.py +584 -0
- nvmath/sparse/_internal/cudss_data_ifc.py +291 -0
- nvmath/sparse/_internal/cudss_utils.py +398 -0
- nvmath/sparse/_internal/sparse_csr_ifc.py +154 -0
- nvmath/sparse/_internal/sparse_format_helpers.py +88 -0
- nvmath/sparse/_internal/sparse_tensor_ifc.py +80 -0
- nvmath/sparse/advanced/__init__.py +7 -0
- nvmath/sparse/advanced/_configuration.py +150 -0
- nvmath/sparse/advanced/direct_solver.py +1705 -0
- nvmath_python-0.5.0.dist-info/METADATA +122 -0
- nvmath_python-0.5.0.dist-info/RECORD +174 -0
- nvmath_python-0.5.0.dist-info/WHEEL +5 -0
- nvmath_python-0.5.0.dist-info/licenses/LICENSE +177 -0
- nvmath_python-0.5.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
# Copyright (c) 2024-2025, NVIDIA CORPORATION & AFFILIATES. ALL RIGHTS RESERVED.
|
|
2
|
+
#
|
|
3
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
|
|
5
|
+
"""
|
|
6
|
+
Factories for create options dataclasses, as well as utilities to add docstring to enum
|
|
7
|
+
classes.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import dataclasses
|
|
11
|
+
from enum import IntEnum
|
|
12
|
+
import re
|
|
13
|
+
from typing import Any, ClassVar
|
|
14
|
+
from collections.abc import Callable
|
|
15
|
+
|
|
16
|
+
import numpy
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def create_options_class_from_enum(
|
|
20
|
+
options_class_name: str,
|
|
21
|
+
enum_class: IntEnum,
|
|
22
|
+
get_attr_dtype: Callable,
|
|
23
|
+
description: str,
|
|
24
|
+
filter_re: str = r"(?P<option_name>.*)",
|
|
25
|
+
):
|
|
26
|
+
"""
|
|
27
|
+
Create an options dataclass from a Python enum class. Names can be filtered if desired.
|
|
28
|
+
|
|
29
|
+
Args:
|
|
30
|
+
options_class_name: Name of the dataclass that will be created.
|
|
31
|
+
|
|
32
|
+
enum_class: The IntEnum class that contains the options for the dataclass.
|
|
33
|
+
|
|
34
|
+
get_attr_dtype: A callable that takes in an enum value as the argument and returns
|
|
35
|
+
the size in bytes of the cuTensorNet.
|
|
36
|
+
|
|
37
|
+
filter_re: A re definition that defines the match named 'option_name'.
|
|
38
|
+
"""
|
|
39
|
+
if r"(?P<option_name>" not in filter_re:
|
|
40
|
+
message = """Incorrect re.
|
|
41
|
+
The re for the filter must contain the named group 'option_name'."""
|
|
42
|
+
raise ValueError(message)
|
|
43
|
+
|
|
44
|
+
# Helper vars for creating attribute docstring.
|
|
45
|
+
doc = f"""A data class for capturing the {description} options.
|
|
46
|
+
|
|
47
|
+
Attributes:
|
|
48
|
+
"""
|
|
49
|
+
indent = " " * 8
|
|
50
|
+
prefix = determine_enum_prefix(enum_class, "_ATTRIBUTE")
|
|
51
|
+
|
|
52
|
+
filter_pattern = re.compile(filter_re)
|
|
53
|
+
option_to_enum: dict[str, IntEnum] = {}
|
|
54
|
+
option_to_dtype: dict[str, Any] = {}
|
|
55
|
+
# mypy claims that IntEnum is not iterable
|
|
56
|
+
for e in enum_class: # type: ignore
|
|
57
|
+
m = filter_pattern.match(e.name)
|
|
58
|
+
if not m:
|
|
59
|
+
continue
|
|
60
|
+
option_name = m.group("option_name").lower()
|
|
61
|
+
option_to_enum[option_name] = e
|
|
62
|
+
option_to_dtype[option_name] = get_attr_dtype(e)
|
|
63
|
+
|
|
64
|
+
# Add docstring for this attribute.
|
|
65
|
+
doc += indent + option_name + ":" + f" See `{prefix + '_' + m.group(0)}`.\n"
|
|
66
|
+
|
|
67
|
+
field: tuple[str, Any, None | dict]
|
|
68
|
+
fields: list[tuple[str, Any, None | dict]] = []
|
|
69
|
+
for option_name, dtype in option_to_dtype.items():
|
|
70
|
+
if numpy.issubdtype(dtype, numpy.integer):
|
|
71
|
+
field = option_name, int | None, dataclasses.field(default=None)
|
|
72
|
+
else:
|
|
73
|
+
field = option_name, Any | None, dataclasses.field(default=None)
|
|
74
|
+
fields.append(field)
|
|
75
|
+
|
|
76
|
+
# Add class attributes.
|
|
77
|
+
|
|
78
|
+
field = "option_to_enum", ClassVar[dict], dataclasses.field(default=option_to_enum)
|
|
79
|
+
fields.append(field)
|
|
80
|
+
|
|
81
|
+
field = "option_to_dtype", ClassVar[dict], dataclasses.field(default=option_to_dtype)
|
|
82
|
+
fields.append(field)
|
|
83
|
+
|
|
84
|
+
# Create the options class.
|
|
85
|
+
options_class = dataclasses.make_dataclass(options_class_name, fields)
|
|
86
|
+
options_class.__doc__ = doc
|
|
87
|
+
|
|
88
|
+
return options_class
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def snake_to_camel(names):
|
|
92
|
+
name = ""
|
|
93
|
+
for i, sub_name in enumerate(names):
|
|
94
|
+
if i == 0:
|
|
95
|
+
name += sub_name.lower()
|
|
96
|
+
else:
|
|
97
|
+
name += sub_name[0].upper() + sub_name[1:]
|
|
98
|
+
name += "_t"
|
|
99
|
+
return name
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def camel_to_snake(name, upper=True):
|
|
103
|
+
"""
|
|
104
|
+
Convert string from camel case to snake style.
|
|
105
|
+
"""
|
|
106
|
+
|
|
107
|
+
def transformer(m):
|
|
108
|
+
prefix = iter(("", "_", ""))
|
|
109
|
+
for i in 1, 3, 5:
|
|
110
|
+
first, second = i, i + 1
|
|
111
|
+
s = next(prefix)
|
|
112
|
+
if m.group(second):
|
|
113
|
+
if m.group(first): # If the second group doesn't exist, the first won't either by the design of the RE.
|
|
114
|
+
s += m.group(first).lower() + "_"
|
|
115
|
+
s += m.group(second).lower()
|
|
116
|
+
break
|
|
117
|
+
return s
|
|
118
|
+
|
|
119
|
+
name = re.sub(r"^([A-Z]*)([A-Z])|(?<!_)([A-Z]*)([A-Z])|([A-Z]*)([A-Z])", transformer, name)
|
|
120
|
+
if upper:
|
|
121
|
+
name = name.upper()
|
|
122
|
+
return name
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def determine_enum_prefix(enum_class, chomp):
|
|
126
|
+
"""
|
|
127
|
+
This function assumes that the convention used to translate C enumerators to Python enum
|
|
128
|
+
names holds.
|
|
129
|
+
"""
|
|
130
|
+
|
|
131
|
+
prefix = enum_class.__module__.split(".")[-1].upper()
|
|
132
|
+
prefix += "_" + camel_to_snake(enum_class.__name__)
|
|
133
|
+
prefix = re.sub(chomp, "", prefix)
|
|
134
|
+
return prefix
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def add_enum_class_doc(enum_class, chomp):
|
|
138
|
+
"""
|
|
139
|
+
Add docstring to enum classes.
|
|
140
|
+
"""
|
|
141
|
+
for e in enum_class:
|
|
142
|
+
e.__doc__ = f"See `{determine_enum_prefix(enum_class, chomp) + '_' + e.name.upper()}`."
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# Copyright (c) 2024-2025, NVIDIA CORPORATION & AFFILIATES. ALL RIGHTS RESERVED.
|
|
2
|
+
#
|
|
3
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
|
|
5
|
+
"""
|
|
6
|
+
Formatters for printing data.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import numpy as np
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class MemoryStr:
|
|
13
|
+
"""
|
|
14
|
+
A simple type to pretty-print memory-like values.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
def __init__(self, memory, base_unit="B"):
|
|
18
|
+
self.memory = memory
|
|
19
|
+
self.base_unit = base_unit
|
|
20
|
+
self.base = 1024
|
|
21
|
+
|
|
22
|
+
def __str__(self):
|
|
23
|
+
"""
|
|
24
|
+
Convert large values to powers of 1024 for readability.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
base, base_unit, memory = self.base, self.base_unit, self.memory
|
|
28
|
+
|
|
29
|
+
if memory < base:
|
|
30
|
+
value, unit = memory, base_unit
|
|
31
|
+
elif memory < base**2:
|
|
32
|
+
value, unit = memory / base, f"Ki{base_unit}"
|
|
33
|
+
elif memory < base**3:
|
|
34
|
+
value, unit = memory / base**2, f"Mi{base_unit}"
|
|
35
|
+
else:
|
|
36
|
+
value, unit = memory / base**3, f"Gi{base_unit}"
|
|
37
|
+
|
|
38
|
+
return f"{value:0.2f} {unit}"
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class FLOPSStr:
|
|
42
|
+
"""
|
|
43
|
+
A simple type to pretty-print FLOP count and FLOP/s-like values.
|
|
44
|
+
"""
|
|
45
|
+
|
|
46
|
+
def __init__(self, flops, base_unit):
|
|
47
|
+
self.flops = flops
|
|
48
|
+
self.base_unit = base_unit
|
|
49
|
+
self.base = 1000
|
|
50
|
+
|
|
51
|
+
def __str__(self):
|
|
52
|
+
"""
|
|
53
|
+
Convert large values to powers of 1000 for readability.
|
|
54
|
+
"""
|
|
55
|
+
|
|
56
|
+
base, base_unit, flops = self.base, self.base_unit, self.flops
|
|
57
|
+
|
|
58
|
+
if flops < base:
|
|
59
|
+
value, unit = flops, base_unit
|
|
60
|
+
elif flops < base**2:
|
|
61
|
+
value, unit = flops / base, f"K{base_unit}"
|
|
62
|
+
elif flops < base**3:
|
|
63
|
+
value, unit = flops / base**2, f"M{base_unit}"
|
|
64
|
+
elif flops < base**4:
|
|
65
|
+
value, unit = flops / base**3, f"G{base_unit}"
|
|
66
|
+
elif flops < base**5:
|
|
67
|
+
value, unit = flops / base**4, f"T{base_unit}"
|
|
68
|
+
else:
|
|
69
|
+
value, unit = flops / base**5, f"P{base_unit}"
|
|
70
|
+
|
|
71
|
+
return f"{value:0.3f} {unit}"
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def array2string(array_like):
|
|
75
|
+
"""
|
|
76
|
+
String representation of an array-like object with possible truncation of "interior"
|
|
77
|
+
values to limit string size.
|
|
78
|
+
|
|
79
|
+
The NumPy function "set_printoptions" can be used to control the display of the array.
|
|
80
|
+
"""
|
|
81
|
+
|
|
82
|
+
return np.array2string(
|
|
83
|
+
np.asanyarray(array_like, dtype=object),
|
|
84
|
+
separator=", ",
|
|
85
|
+
# NumPy hates empty strings so we print 'None' instead.
|
|
86
|
+
formatter={"object": lambda s: s if s != "" else "None"},
|
|
87
|
+
)
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# Copyright (c) 2024-2025, NVIDIA CORPORATION & AFFILIATES. ALL RIGHTS RESERVED.
|
|
2
|
+
#
|
|
3
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
|
|
5
|
+
"""
|
|
6
|
+
Memory specification regular expression.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
__all__ = ["MEM_LIMIT_RE_PCT", "MEM_LIMIT_RE_VAL", "MEM_LIMIT_DOC", "check_memory_str"]
|
|
10
|
+
|
|
11
|
+
import re
|
|
12
|
+
|
|
13
|
+
MEM_LIMIT_RE_PCT = re.compile(r"(?P<value>[+]?[0-9]*\.?[0-9]+(?:[eE][-+]?[0-9]+)?)\s*%\s*$")
|
|
14
|
+
MEM_LIMIT_RE_VAL = re.compile(
|
|
15
|
+
r"(?P<value>[+]?[0-9]*\.?[0-9]+(?:[eE][-+]?[0-9]+)?)\s*(?P<units>[kmg])?(?P<binary>(?<=[kmg])i)?b\s*$",
|
|
16
|
+
re.IGNORECASE,
|
|
17
|
+
)
|
|
18
|
+
MEM_LIMIT_DOC = """The {kind} must be specified in one of the following forms:
|
|
19
|
+
(1) A number (int or float). If the number is a float between 0 and 1 inclusive, the
|
|
20
|
+
{kind} is interpreted as a fraction of the total device memory; otherwise it is
|
|
21
|
+
interpreted as the number of bytes of memory, with float value being cast to int.
|
|
22
|
+
Examples: 0.75, 50E6, 50000000, ...
|
|
23
|
+
(2) A string containing a positive value followed by B, kB, MB, or GB for powers of 1000.
|
|
24
|
+
Examples: "0.05 GB", "50 MB", "50000000 B" ...
|
|
25
|
+
(3) A string containing a positive value followed by kiB, MiB, or GiB for powers of 1024.
|
|
26
|
+
Examples: "0.05 GiB", "51.2 MiB", "53687091 B" ...
|
|
27
|
+
(4) A string with value in the range [0, 100] followed by a % symbol.
|
|
28
|
+
Examples: "26%","82%", ...
|
|
29
|
+
|
|
30
|
+
Whitespace between values and units is optional.
|
|
31
|
+
|
|
32
|
+
The provided {kind} is "{value}".
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def check_memory_str(value, kind):
|
|
37
|
+
"""
|
|
38
|
+
Check if the memory specification string is valid.
|
|
39
|
+
|
|
40
|
+
value = the memory specification string.
|
|
41
|
+
kind = a string denoting the type of memory being checked, used in error messages.
|
|
42
|
+
"""
|
|
43
|
+
if not isinstance(value, int | float):
|
|
44
|
+
m1 = MEM_LIMIT_RE_PCT.match(value)
|
|
45
|
+
if m1:
|
|
46
|
+
factor = float(m1.group("value"))
|
|
47
|
+
if factor < 0 or factor > 100:
|
|
48
|
+
raise ValueError(f"The {kind} percentage must be in the range [0, 100].")
|
|
49
|
+
m2 = MEM_LIMIT_RE_VAL.match(value)
|
|
50
|
+
if not (m1 or m2):
|
|
51
|
+
raise ValueError(MEM_LIMIT_DOC.format(kind=kind, value=value))
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
# Copyright (c) 2024-2025, NVIDIA CORPORATION & AFFILIATES. ALL RIGHTS RESERVED.
|
|
2
|
+
#
|
|
3
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
|
|
5
|
+
"""
|
|
6
|
+
An abstract interface to certain package-provided operations.
|
|
7
|
+
|
|
8
|
+
Even though cuda.core does not have the concept of a "current stream" or a "stream context",
|
|
9
|
+
we promise to honor third-party CUDA stream contexts if they match the input operand's
|
|
10
|
+
package. The interface class defined in this module wraps around those stream concepts to
|
|
11
|
+
make them uniform.
|
|
12
|
+
|
|
13
|
+
The strategy is to use cuda.core everywhere internally except for context managers which
|
|
14
|
+
will need to wrap back around to the external implementation.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
__all__ = ["Package", "StreamHolder"]
|
|
18
|
+
|
|
19
|
+
from abc import ABC, abstractmethod
|
|
20
|
+
from collections.abc import Hashable
|
|
21
|
+
from contextlib import AbstractContextManager
|
|
22
|
+
from dataclasses import dataclass
|
|
23
|
+
from typing import Generic, Protocol, TypeVar
|
|
24
|
+
|
|
25
|
+
import cuda.core.experimental as ccx
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class AnyStream(Hashable, Protocol):
|
|
29
|
+
"""Any supported Stream object such as a Stream from cuda.core, CuPy, or PyTorch."""
|
|
30
|
+
|
|
31
|
+
# This empty protocol class is just a placeholder to make type checking more helpful.
|
|
32
|
+
# Since the class is empty, it doesn't assume anything is true about the implementation
|
|
33
|
+
# classes.
|
|
34
|
+
pass
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
"""
|
|
38
|
+
A generic type for the third-party Stream which a given Package implementation wraps
|
|
39
|
+
around.
|
|
40
|
+
"""
|
|
41
|
+
Stream = TypeVar("Stream")
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class Package(ABC, Generic[Stream]):
|
|
45
|
+
@staticmethod
|
|
46
|
+
@abstractmethod
|
|
47
|
+
def get_current_stream(device_id: int) -> Stream:
|
|
48
|
+
"""
|
|
49
|
+
Obtain the current stream on the device.
|
|
50
|
+
|
|
51
|
+
Args:
|
|
52
|
+
device_id: The id (ordinal) of the device.
|
|
53
|
+
"""
|
|
54
|
+
raise NotImplementedError
|
|
55
|
+
|
|
56
|
+
@staticmethod
|
|
57
|
+
@abstractmethod
|
|
58
|
+
def to_stream_pointer(stream: Stream) -> int:
|
|
59
|
+
"""
|
|
60
|
+
Obtain the stream pointer.
|
|
61
|
+
|
|
62
|
+
Args:
|
|
63
|
+
stream: The stream object.
|
|
64
|
+
"""
|
|
65
|
+
raise NotImplementedError
|
|
66
|
+
|
|
67
|
+
@staticmethod
|
|
68
|
+
@abstractmethod
|
|
69
|
+
def to_stream_context(stream: Stream) -> AbstractContextManager[Stream]:
|
|
70
|
+
"""
|
|
71
|
+
Create a context manager from the stream.
|
|
72
|
+
|
|
73
|
+
Args:
|
|
74
|
+
stream: The stream object.
|
|
75
|
+
"""
|
|
76
|
+
raise NotImplementedError
|
|
77
|
+
|
|
78
|
+
@staticmethod
|
|
79
|
+
@abstractmethod
|
|
80
|
+
def create_external_stream(device_id: int, stream_ptr: int) -> Stream:
|
|
81
|
+
"""
|
|
82
|
+
Wrap a stream pointer into an external stream object.
|
|
83
|
+
|
|
84
|
+
Args:
|
|
85
|
+
device_id: The id (ordinal) of the device.
|
|
86
|
+
stream: The stream pointer (int) to be wrapped.
|
|
87
|
+
"""
|
|
88
|
+
raise NotImplementedError
|
|
89
|
+
|
|
90
|
+
@classmethod
|
|
91
|
+
def create_stream(cls, external: Stream) -> ccx.Stream:
|
|
92
|
+
"""
|
|
93
|
+
Wrap an external Stream object into a cuda.core.Stream.
|
|
94
|
+
|
|
95
|
+
Args:
|
|
96
|
+
external: The external Stream object.
|
|
97
|
+
"""
|
|
98
|
+
return ccx.Stream.from_handle(cls.to_stream_pointer(external))
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
@dataclass
|
|
102
|
+
class StreamHolder(Generic[Stream]):
|
|
103
|
+
"""A data class for easing CUDA stream manipulation.
|
|
104
|
+
|
|
105
|
+
Attributes:
|
|
106
|
+
ctx: A context manager for using the specified stream.
|
|
107
|
+
device_id (int): The device ID where the encapsulated stream locates.
|
|
108
|
+
external: A foreign object that holds the stream alive.
|
|
109
|
+
obj: The cuda.core Stream object wrapping external.
|
|
110
|
+
package (str): The name of the package to which the external stream belongs.
|
|
111
|
+
ptr (int): The address of the underlying ``cudaStream_t`` object.
|
|
112
|
+
"""
|
|
113
|
+
|
|
114
|
+
ctx: AbstractContextManager[Stream]
|
|
115
|
+
device_id: int
|
|
116
|
+
external: Stream
|
|
117
|
+
obj: ccx.Stream
|
|
118
|
+
package: str
|
|
119
|
+
ptr: int
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# Copyright (c) 2024-2025, NVIDIA CORPORATION & AFFILIATES. ALL RIGHTS RESERVED.
|
|
2
|
+
#
|
|
3
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
|
|
5
|
+
"""
|
|
6
|
+
Interface to cuda.core operations.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
__all__ = ["CUDAPackage"]
|
|
10
|
+
|
|
11
|
+
import contextlib
|
|
12
|
+
import typing
|
|
13
|
+
|
|
14
|
+
import cuda.core.experimental as ccx
|
|
15
|
+
|
|
16
|
+
from .package_ifc import Package
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class CUDAPackage(Package[ccx.Stream]):
|
|
20
|
+
@staticmethod
|
|
21
|
+
def get_current_stream(device_id: int):
|
|
22
|
+
message = "cuda.core has no concept of a current stream or a stream context."
|
|
23
|
+
raise NotImplementedError(message)
|
|
24
|
+
|
|
25
|
+
@staticmethod
|
|
26
|
+
def to_stream_pointer(stream: ccx.Stream) -> int: # type: ignore[override]
|
|
27
|
+
return int(stream.handle)
|
|
28
|
+
|
|
29
|
+
@staticmethod
|
|
30
|
+
def to_stream_context(stream: ccx.Stream): # type: ignore[override]
|
|
31
|
+
@contextlib.contextmanager
|
|
32
|
+
def stream_context() -> typing.Iterator[ccx.Stream]:
|
|
33
|
+
yield stream
|
|
34
|
+
|
|
35
|
+
return stream_context
|
|
36
|
+
|
|
37
|
+
@staticmethod
|
|
38
|
+
def create_external_stream(device_id: int, stream_ptr: int) -> ccx.Stream:
|
|
39
|
+
return ccx.Stream.from_handle(stream_ptr)
|
|
40
|
+
|
|
41
|
+
@classmethod
|
|
42
|
+
def create_stream(cls, external: ccx.Stream) -> ccx.Stream: # type: ignore[override]
|
|
43
|
+
return external
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# Copyright (c) 2024-2025, NVIDIA CORPORATION & AFFILIATES. ALL RIGHTS RESERVED.
|
|
2
|
+
#
|
|
3
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
|
|
5
|
+
"""
|
|
6
|
+
Interface to CuPy operations.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
__all__ = ["CupyPackage"]
|
|
10
|
+
|
|
11
|
+
import cupy as cp
|
|
12
|
+
|
|
13
|
+
from . import utils
|
|
14
|
+
from .package_ifc import Package
|
|
15
|
+
|
|
16
|
+
# Using the functional API is faster than setting a device context
|
|
17
|
+
if int(cp.__version__.split(".")[0]) >= 13:
|
|
18
|
+
_get_current_stream = cp.cuda.get_current_stream
|
|
19
|
+
else:
|
|
20
|
+
|
|
21
|
+
def _get_current_stream(device_id: int):
|
|
22
|
+
with utils.device_ctx(device_id):
|
|
23
|
+
stream = cp.cuda.get_current_stream()
|
|
24
|
+
return stream
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
# Monkey patch older versions of CuPy, so that Streams are hashable
|
|
28
|
+
# NOTE: We choose not to patch the BaseStream/_BaseStream class because of name change
|
|
29
|
+
if int(cp.__version__.split(".")[0]) < 11:
|
|
30
|
+
cp.cuda.Stream.__hash__ = lambda self: hash(self.ptr)
|
|
31
|
+
cp.cuda.ExternalStream.__hash__ = lambda self: hash(self.ptr)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class CupyPackage(Package[cp.cuda.Stream]):
|
|
35
|
+
@staticmethod
|
|
36
|
+
def get_current_stream(device_id: int):
|
|
37
|
+
return _get_current_stream(device_id)
|
|
38
|
+
|
|
39
|
+
@staticmethod
|
|
40
|
+
def to_stream_pointer(stream: cp.cuda.Stream) -> int:
|
|
41
|
+
return stream.ptr
|
|
42
|
+
|
|
43
|
+
@staticmethod
|
|
44
|
+
def to_stream_context(stream: cp.cuda.Stream):
|
|
45
|
+
return stream
|
|
46
|
+
|
|
47
|
+
@staticmethod
|
|
48
|
+
def create_external_stream(device_id: int, stream_ptr: int) -> cp.cuda.ExternalStream:
|
|
49
|
+
return cp.cuda.ExternalStream(stream_ptr)
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# Copyright (c) 2024-2025, NVIDIA CORPORATION & AFFILIATES. ALL RIGHTS RESERVED.
|
|
2
|
+
#
|
|
3
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
|
|
5
|
+
"""
|
|
6
|
+
Interface to Torch operations.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
__all__ = ["TorchPackage"]
|
|
10
|
+
|
|
11
|
+
import torch
|
|
12
|
+
|
|
13
|
+
from .package_ifc import Package
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class TorchPackage(Package[torch.cuda.Stream]):
|
|
17
|
+
@staticmethod
|
|
18
|
+
def get_current_stream(device_id: int) -> torch.cuda.Stream:
|
|
19
|
+
return torch.cuda.current_stream(device=device_id)
|
|
20
|
+
|
|
21
|
+
@staticmethod
|
|
22
|
+
def to_stream_pointer(stream: torch.cuda.Stream) -> int: # type: ignore[override]
|
|
23
|
+
return stream.cuda_stream
|
|
24
|
+
|
|
25
|
+
@staticmethod
|
|
26
|
+
def to_stream_context(stream: torch.cuda.Stream) -> torch.cuda.StreamContext: # type: ignore[override]
|
|
27
|
+
return torch.cuda.stream(stream)
|
|
28
|
+
|
|
29
|
+
@staticmethod
|
|
30
|
+
def create_external_stream(device_id: int, stream_ptr: int):
|
|
31
|
+
return torch.cuda.ExternalStream(stream_ptr, device=device_id)
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# Copyright (c) 2024-2025, NVIDIA CORPORATION & AFFILIATES. ALL RIGHTS RESERVED.
|
|
2
|
+
#
|
|
3
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
|
|
5
|
+
"""
|
|
6
|
+
Create map from package names to package interface objects.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
__all__ = ["PACKAGE", "AnyStream", "StreamHolder"]
|
|
10
|
+
|
|
11
|
+
from .package_ifc import Package, AnyStream, StreamHolder
|
|
12
|
+
from .package_ifc_cuda import CUDAPackage
|
|
13
|
+
|
|
14
|
+
PACKAGE: dict[str, type[Package]] = {"cuda": CUDAPackage}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
# Copyright (c) 2024-2025, NVIDIA CORPORATION & AFFILIATES. ALL RIGHTS RESERVED.
|
|
2
|
+
#
|
|
3
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
|
|
5
|
+
"""
|
|
6
|
+
Interface to seamlessly use tensors (or ndarray-like objects) from different libraries.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations # allows typehint of class methods to return the self class
|
|
10
|
+
|
|
11
|
+
from abc import ABC, abstractmethod
|
|
12
|
+
from collections.abc import Callable, Sequence
|
|
13
|
+
from typing import Any, Literal, Generic, Protocol, TypeVar
|
|
14
|
+
from types import ModuleType
|
|
15
|
+
|
|
16
|
+
from . import typemaps
|
|
17
|
+
from .package_ifc import StreamHolder
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class AnyTensor(Protocol):
|
|
21
|
+
"""Any supported external tensor object such as NumPy, CuPy, and PyTorch arrays."""
|
|
22
|
+
|
|
23
|
+
# This empty protocol class is just a placeholder to make type checking more helpful.
|
|
24
|
+
# Since the class is empty, it doesn't assume anything is true about the implementation
|
|
25
|
+
# classes. Used to type hint any object that TensorHolder knows how to wrap.
|
|
26
|
+
pass
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
"""
|
|
30
|
+
A generic type for the third-party Tensor which a given TensorHolder implementation wraps
|
|
31
|
+
around.
|
|
32
|
+
"""
|
|
33
|
+
Tensor = TypeVar("Tensor")
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class TensorHolder(ABC, Generic[Tensor]):
|
|
37
|
+
"""
|
|
38
|
+
A simple wrapper type for tensors to make the API package-agnostic.
|
|
39
|
+
|
|
40
|
+
Methods of a TensorHolder should always return a TensorHolder instead of a Tensor (one
|
|
41
|
+
of the wrapped classes) in order to prevent implementation details from the various
|
|
42
|
+
Tensor implementations from leaking into nvmath-python.
|
|
43
|
+
|
|
44
|
+
Tensors from the user should be immediately wrapped with a TensorHolder and should
|
|
45
|
+
remain wrapped until just before returning to the user.
|
|
46
|
+
"""
|
|
47
|
+
|
|
48
|
+
name: str
|
|
49
|
+
module: ModuleType
|
|
50
|
+
name_to_dtype: dict[str, Any]
|
|
51
|
+
|
|
52
|
+
def __init__(self, tensor: Tensor):
|
|
53
|
+
self.tensor: Tensor = tensor
|
|
54
|
+
|
|
55
|
+
@property
|
|
56
|
+
@abstractmethod
|
|
57
|
+
def data_ptr(self) -> int:
|
|
58
|
+
raise NotImplementedError
|
|
59
|
+
|
|
60
|
+
@property
|
|
61
|
+
@abstractmethod
|
|
62
|
+
def device(self) -> Literal["cuda"] | Literal["cpu"]:
|
|
63
|
+
"""The type of the device which stores the tensor."""
|
|
64
|
+
raise NotImplementedError
|
|
65
|
+
|
|
66
|
+
@property
|
|
67
|
+
@abstractmethod
|
|
68
|
+
def device_id(self) -> int | Literal["cpu"]:
|
|
69
|
+
"""The device ordinal of the device storing the tensor."""
|
|
70
|
+
raise NotImplementedError
|
|
71
|
+
|
|
72
|
+
@property
|
|
73
|
+
@abstractmethod
|
|
74
|
+
def dtype(self) -> str:
|
|
75
|
+
"""Name of the data type"""
|
|
76
|
+
raise NotImplementedError
|
|
77
|
+
|
|
78
|
+
@classmethod
|
|
79
|
+
@abstractmethod
|
|
80
|
+
def empty(cls, shape: Sequence[int], device_id: int | Literal["cpu"], **context: Any) -> TensorHolder[Tensor]:
|
|
81
|
+
"""Create an empty TensorHolder of the specified shape and data type."""
|
|
82
|
+
raise NotImplementedError
|
|
83
|
+
|
|
84
|
+
@property
|
|
85
|
+
@abstractmethod
|
|
86
|
+
def shape(self) -> Sequence[int]:
|
|
87
|
+
raise NotImplementedError
|
|
88
|
+
|
|
89
|
+
@property
|
|
90
|
+
@abstractmethod
|
|
91
|
+
def size(self) -> int:
|
|
92
|
+
raise NotImplementedError
|
|
93
|
+
|
|
94
|
+
@property
|
|
95
|
+
@abstractmethod
|
|
96
|
+
def strides(self) -> Sequence[int]:
|
|
97
|
+
raise NotImplementedError
|
|
98
|
+
|
|
99
|
+
@abstractmethod
|
|
100
|
+
def to(self, device_id: int | Literal["cpu"], stream_holder: StreamHolder | None) -> TensorHolder:
|
|
101
|
+
"""Copy the TensorHolder to a different device.
|
|
102
|
+
|
|
103
|
+
No copy is performed if the TensorHolder is already on the requested device.
|
|
104
|
+
"""
|
|
105
|
+
raise NotImplementedError
|
|
106
|
+
|
|
107
|
+
@abstractmethod
|
|
108
|
+
def copy_(self, src: TensorHolder, stream_holder: StreamHolder | None) -> None:
|
|
109
|
+
"""Overwrite self.tensor (in-place) with a copy of src."""
|
|
110
|
+
raise NotImplementedError
|
|
111
|
+
|
|
112
|
+
@staticmethod
|
|
113
|
+
def create_name_dtype_map(conversion_function: Callable[[str], Any], exception_type: type[Exception]) -> dict[str, Any]:
|
|
114
|
+
"""
|
|
115
|
+
Create a map between CUDA data type names and the corresponding package dtypes for
|
|
116
|
+
supported data types.
|
|
117
|
+
"""
|
|
118
|
+
names = typemaps.NAME_TO_DATA_TYPE.keys()
|
|
119
|
+
name_to_dtype = {}
|
|
120
|
+
for name in names:
|
|
121
|
+
try:
|
|
122
|
+
name_to_dtype[name] = conversion_function(name)
|
|
123
|
+
except exception_type:
|
|
124
|
+
pass
|
|
125
|
+
return name_to_dtype
|
|
126
|
+
|
|
127
|
+
@abstractmethod
|
|
128
|
+
def istensor(self) -> bool:
|
|
129
|
+
"""Return whether self.tensor is the expected type."""
|
|
130
|
+
raise NotImplementedError
|
|
131
|
+
|
|
132
|
+
@abstractmethod
|
|
133
|
+
def reshape(self, shape: Sequence[int], *, copy: bool | None = None) -> TensorHolder[Tensor]:
|
|
134
|
+
"""Reshapes tensor without changing its data.
|
|
135
|
+
|
|
136
|
+
Args:
|
|
137
|
+
shape: a new shape compatible with the original shape.
|
|
138
|
+
|
|
139
|
+
copy (Optional[bool]): whether or not to copy the input tensor. If True, the
|
|
140
|
+
function must always copy. If False, the function must never copy. If None,
|
|
141
|
+
the function must avoid copying, if possible, and may copy otherwise.
|
|
142
|
+
"""
|
|
143
|
+
raise NotImplementedError
|