Skip to content

Commit c4a5647

Browse files
authored
Move runtime's internal and cython layers to cybind (NVIDIA#2261)
* Move runtime's internal and cython layers to cybind * Attempt to fix build * Another attempt to fix the build * Attempt to fix bindings * Fix Windows build * ABI fixes * Fix Windows build (again) * Fixes after merge * Rebuild after merge on the cybind side
1 parent 631aca9 commit c4a5647

25 files changed

Lines changed: 19757 additions & 21850 deletions

.gitignore

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -25,23 +25,16 @@ cuda_core/tests/test_binaries/*.a
2525
cuda_core/tests/test_binaries/*.lib
2626

2727
# CUDA Python specific (auto-generated)
28-
cuda_bindings/cuda/bindings/_bindings/cyruntime.pxd
29-
cuda_bindings/cuda/bindings/_bindings/cyruntime.pyx
30-
cuda_bindings/cuda/bindings/_bindings/cyruntime_ptds.pxd
31-
cuda_bindings/cuda/bindings/_bindings/cyruntime_ptds.pyx
28+
cuda_bindings/cuda/bindings/_internal/cudla.pyx
3229
cuda_bindings/cuda/bindings/_internal/driver.pyx
3330
cuda_bindings/cuda/bindings/_internal/nvrtc.pyx
3431
cuda_bindings/cuda/bindings/_internal/cufile.pyx
3532
cuda_bindings/cuda/bindings/_internal/nvfatbin.pyx
3633
cuda_bindings/cuda/bindings/_internal/nvjitlink.pyx
3734
cuda_bindings/cuda/bindings/_internal/nvml.pyx
3835
cuda_bindings/cuda/bindings/_internal/nvvm.pyx
39-
cuda_bindings/cuda/bindings/cyruntime.pxd
40-
cuda_bindings/cuda/bindings/cyruntime.pyx
41-
cuda_bindings/cuda/bindings/cyruntime_functions.pxi
42-
cuda_bindings/cuda/bindings/cyruntime_types.pxi
43-
cuda_bindings/cuda/bindings/runtime.pxd
44-
cuda_bindings/cuda/bindings/runtime.pyx
36+
cuda_bindings/cuda/bindings/_internal/runtime.pyx
37+
cuda_bindings/cuda/bindings/_internal/runtime_ptds.pyx
4538
cuda_bindings/cuda/bindings/utils/_get_handle.pyx
4639

4740
# Version files from setuptools_scm

cuda_bindings/AGENTS.md

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,21 +18,18 @@ subpackage in the `cuda-python` monorepo.
1818
glue and loader helpers used by public modules.
1919
- **Platform internals**: `cuda/bindings/_internal/` contains
2020
platform-specific implementation files and support code.
21-
- **Build/codegen backend**: `build_hooks.py` drives header parsing, template
22-
expansion, extension configuration, and Cythonization.
21+
- **Build backend**: `build_hooks.py` drives extension configuration and
22+
Cythonization.
2323

2424
## Generated-source workflow
2525

2626
- **Do not hand-edit generated binding files**: many files under
27-
`cuda/bindings/` (including `*.pyx`, `*.pxd`, `*.pyx.in`, and `*.pxd.in`)
28-
are generated artifacts.
27+
`cuda/bindings/` (including `*.pyx` and `*.pxd`) are generated artifacts.
2928
- **Generated files are synchronized from another repository**: changes to these
3029
files in this repo are expected to be overwritten by the next sync.
3130
- **If generated output must change**: make the change at the generation source
3231
and sync the updated artifacts back here, rather than patching generated files
3332
directly in this repo.
34-
- **Header-driven generation**: parser behavior and required CUDA headers are
35-
defined in `build_hooks.py`; update those rules when introducing new symbols.
3633
- **Platform split files**: keep `_linux.pyx` and `_windows.pyx` variants
3734
aligned when behavior should be equivalent.
3835

@@ -48,9 +45,8 @@ subpackage in the `cuda-python` monorepo.
4845
## Build and environment notes
4946

5047
- `CUDA_HOME` or `CUDA_PATH` must point to a valid CUDA Toolkit for source
51-
builds that parse headers.
48+
builds.
5249
- `CUDA_PYTHON_PARALLEL_LEVEL` controls build parallelism.
53-
- `CUDA_PYTHON_PARSER_CACHING` controls parser-cache behavior during generation.
5450
- Runtime behavior is affected by
5551
`CUDA_PYTHON_CUDA_PER_THREAD_DEFAULT_STREAM` and
5652
`CUDA_PYTHON_DISABLE_MAJOR_VERSION_WARNING`.

cuda_bindings/build_hooks.py

Lines changed: 14 additions & 255 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
# SPDX-License-Identifier: Apache-2.0
33

44
# This module implements basic PEP 517 backend support to defer CUDA-dependent
5-
# logic (header parsing, code generation, cythonization) to build time. See:
5+
# logic (cythonization) to build time. See:
66
# - https://peps.python.org/pep-0517/
77
# - https://setuptools.pypa.io/en/latest/build_meta.html#dynamic-build-dependencies-and-other-build-meta-tweaks
88
# - https://github.com/NVIDIA/cuda-python/issues/1635
@@ -74,207 +74,6 @@ def _get_cuda_path() -> str:
7474
return cuda_path
7575

7676

77-
# -----------------------------------------------------------------------
78-
# Header parsing helpers (called only from _build_cuda_bindings)
79-
80-
_REQUIRED_HEADERS = {
81-
"runtime": [
82-
"driver_types.h",
83-
"vector_types.h",
84-
"cuda_runtime.h",
85-
"surface_types.h",
86-
"texture_types.h",
87-
"library_types.h",
88-
"cuda_runtime_api.h",
89-
"device_types.h",
90-
"driver_functions.h",
91-
"cuda_profiler_api.h",
92-
],
93-
# nvrtc: headers no longer parsed at build time (pre-generated by cybind).
94-
# During compilation, Cython will reference C headers that are not
95-
# explicitly parsed above. These are the known dependencies:
96-
#
97-
# - crt/host_defines.h
98-
# - builtin_types.h
99-
# - cuda_device_runtime_api.h
100-
}
101-
102-
103-
class _Struct:
104-
def __init__(self, name, members):
105-
self._name = name
106-
self._member_names = []
107-
self._member_types = []
108-
self._member_declarators = []
109-
for var_name, var_type, _ in members:
110-
base_type = var_type[0]
111-
base_type = base_type.removeprefix("struct ")
112-
base_type = base_type.removeprefix("union ")
113-
114-
self._member_names += [var_name]
115-
self._member_types += [base_type]
116-
self._member_declarators += [tuple(var_type[1:])]
117-
118-
def member_type(self, member_name):
119-
try:
120-
return self._member_types[self._member_names.index(member_name)]
121-
except ValueError:
122-
return None
123-
124-
def member_array_length(self, member_name):
125-
try:
126-
declarators = self._member_declarators[self._member_names.index(member_name)]
127-
except ValueError:
128-
return None
129-
130-
for declarator in declarators:
131-
if isinstance(declarator, list) and len(declarator) == 1:
132-
return declarator[0]
133-
return None
134-
135-
def discoverMembers(self, memberDict, prefix, seen=None):
136-
if seen is None:
137-
seen = set()
138-
elif self._name in seen:
139-
return []
140-
141-
discovered = []
142-
next_seen = set(seen)
143-
next_seen.add(self._name)
144-
145-
for memberName, memberType in zip(self._member_names, self._member_types):
146-
if memberName:
147-
discovered.append(".".join([prefix, memberName]))
148-
149-
t = memberType.replace("const ", "").replace("volatile ", "").strip().rstrip(" *")
150-
if t in memberDict and t != self._name:
151-
discovered += memberDict[t].discoverMembers(
152-
memberDict, discovered[-1] if memberName else prefix, next_seen
153-
)
154-
155-
return discovered
156-
157-
def __repr__(self):
158-
return f"{self._name}: {self._member_names} with types {self._member_types}"
159-
160-
161-
def _fetch_header_paths(required_headers, include_path_list):
162-
header_dict = {}
163-
missing_headers = []
164-
for library, header_list in required_headers.items():
165-
header_paths = []
166-
for header in header_list:
167-
path_candidate = [os.path.join(path, header) for path in include_path_list]
168-
for path in path_candidate:
169-
if os.path.exists(path):
170-
header_paths += [path]
171-
break
172-
else:
173-
missing_headers += [header]
174-
175-
header_dict[library] = header_paths
176-
177-
if missing_headers:
178-
error_message = "Couldn't find required headers: "
179-
error_message += ", ".join(missing_headers)
180-
cuda_path = _get_cuda_path()
181-
raise RuntimeError(f'{error_message}\nIs CUDA_PATH setup correctly? (CUDA_PATH="{cuda_path}")')
182-
183-
return header_dict
184-
185-
186-
def _parse_headers(header_dict, include_path_list, parser_caching):
187-
from pyclibrary import CParser
188-
189-
found_types = []
190-
found_functions = []
191-
found_values = []
192-
found_struct = []
193-
struct_list = {}
194-
195-
replace = {
196-
" __device_builtin__ ": " ",
197-
"CUDARTAPI ": " ",
198-
"typedef __device_builtin__ enum cudaError cudaError_t;": "typedef cudaError cudaError_t;",
199-
"typedef __device_builtin__ enum cudaOutputMode cudaOutputMode_t;": "typedef cudaOutputMode cudaOutputMode_t;",
200-
"typedef enum cudaError cudaError_t;": "typedef cudaError cudaError_t;",
201-
"typedef enum cudaOutputMode cudaOutputMode_t;": "typedef cudaOutputMode cudaOutputMode_t;",
202-
"typedef enum cudaDataType_t cudaDataType_t;": "",
203-
"typedef enum libraryPropertyType_t libraryPropertyType_t;": "",
204-
" enum ": " ",
205-
", enum ": ", ",
206-
"\\(enum ": "(",
207-
# Since we only support 64 bit architectures, we can inline the sizeof(T*) to 8 and then compute the
208-
# result in Python. The arithmetic expression is preserved to help with clarity and understanding
209-
r"char reserved\[52 - sizeof\(CUcheckpointGpuPair \*\)\];": rf"char reserved[{52 - 8}];",
210-
r"char reserved\[64 - sizeof\(CUcheckpointGpuPair \*\) - sizeof\(unsigned int\)\];": rf"char reserved[{64 - 8 - 4}];",
211-
}
212-
213-
print(f'Parsing headers in "{include_path_list}" (Caching = {parser_caching})', flush=True)
214-
for library, header_paths in header_dict.items():
215-
print(f"Parsing {library} headers", flush=True)
216-
parser = CParser(
217-
header_paths, cache="./cache_{}".format(library.split(".")[0]) if parser_caching else None, replace=replace
218-
)
219-
220-
if library == "driver":
221-
CUDA_VERSION = parser.defs["macros"].get("CUDA_VERSION", "Unknown")
222-
print(f"Found CUDA_VERSION: {CUDA_VERSION}", flush=True)
223-
224-
found_types += set(parser.defs["types"])
225-
found_types += set(parser.defs["structs"])
226-
found_types += set(parser.defs["unions"])
227-
found_types += set(parser.defs["enums"])
228-
found_functions += set(parser.defs["functions"])
229-
found_values += set(parser.defs["values"])
230-
231-
for key, value in parser.defs["structs"].items():
232-
struct_list[key] = _Struct(key, value["members"])
233-
for key, value in parser.defs["unions"].items():
234-
struct_list[key] = _Struct(key, value["members"])
235-
236-
for key, value in struct_list.items():
237-
if key.startswith(("anon_union", "anon_struct")):
238-
continue
239-
240-
found_struct += [key]
241-
discovered = value.discoverMembers(struct_list, key)
242-
if discovered:
243-
found_struct += discovered
244-
245-
# TODO(#1312): make this work properly
246-
found_types.append("CUstreamAtomicReductionDataType_enum")
247-
248-
return found_types, found_functions, found_values, found_struct, struct_list
249-
250-
251-
# -----------------------------------------------------------------------
252-
# Code generation helpers
253-
254-
255-
def _fetch_input_files(path):
256-
return [os.path.join(path, f) for f in os.listdir(path) if f.endswith(".in")]
257-
258-
259-
def _generate_output(infile, template_vars):
260-
from Cython import Tempita
261-
262-
assert infile.endswith(".in")
263-
outfile = infile[:-3]
264-
265-
with open(infile, encoding="utf-8") as f:
266-
pxdcontent = Tempita.Template(f.read()).substitute(template_vars)
267-
268-
if os.path.exists(outfile):
269-
with open(outfile, encoding="utf-8") as f:
270-
if f.read() == pxdcontent:
271-
print(f"Skipping {infile} (No change)", flush=True)
272-
return
273-
with open(outfile, "w", encoding="utf-8") as f:
274-
print(f"Generating {infile}", flush=True)
275-
f.write(pxdcontent)
276-
277-
27877
# -----------------------------------------------------------------------
27978
# Extension preparation helpers
28079

@@ -328,9 +127,8 @@ def _prep_extensions(sources, libraries, include_dirs, library_dirs, extra_compi
328127
def _build_cuda_bindings(debug=False):
329128
"""Build all cuda-bindings extensions.
330129
331-
All CUDA-dependent logic (header parsing, code generation, cythonization)
332-
is deferred to this function so that metadata queries do not require a
333-
CUDA toolkit installation.
130+
All CUDA-dependent logic (cythonization) is deferred to this function so
131+
that metadata queries do not require a CUDA toolkit installation.
334132
"""
335133
from Cython.Build import cythonize
336134

@@ -348,54 +146,10 @@ def _build_cuda_bindings(debug=False):
348146
else:
349147
nthreads = int(os.environ.get("CUDA_PYTHON_PARALLEL_LEVEL", "0") or "0")
350148

351-
parser_caching = bool(os.environ.get("CUDA_PYTHON_PARSER_CACHING", False))
352149
compile_for_coverage = bool(int(os.environ.get("CUDA_PYTHON_COVERAGE", "0")))
353150

354-
# Parse CUDA headers
355-
include_path_list = [os.path.join(cuda_path, "include")]
356-
header_dict = _fetch_header_paths(_REQUIRED_HEADERS, include_path_list)
357-
found_types, found_functions, found_values, found_struct, struct_list = _parse_headers(
358-
header_dict, include_path_list, parser_caching
359-
)
360-
struct_field_types = {}
361-
struct_field_array_lengths = {}
362-
for struct_name, struct in struct_list.items():
363-
for member_name in struct._member_names:
364-
key = f"{struct_name}.{member_name}"
365-
struct_field_types[key] = struct.member_type(member_name)
366-
struct_field_array_lengths[key] = struct.member_array_length(member_name)
367-
368-
# Generate code from .in templates
369-
path_list = [
370-
os.path.join("cuda"),
371-
os.path.join("cuda", "bindings"),
372-
os.path.join("cuda", "bindings", "_bindings"),
373-
os.path.join("cuda", "bindings", "_internal"),
374-
os.path.join("cuda", "bindings", "_lib"),
375-
os.path.join("cuda", "bindings", "utils"),
376-
]
377-
input_files = []
378-
for path in path_list:
379-
input_files += _fetch_input_files(path)
380-
381-
import platform
382-
383-
template_vars = {
384-
"found_types": found_types,
385-
"found_functions": found_functions,
386-
"found_values": found_values,
387-
"found_struct": found_struct,
388-
"struct_list": struct_list,
389-
"struct_field_types": struct_field_types,
390-
"struct_field_array_lengths": struct_field_array_lengths,
391-
"os": os,
392-
"sys": sys,
393-
"platform": platform,
394-
}
395-
for file in input_files:
396-
_generate_output(file, template_vars)
397-
398151
# Prepare compile/link arguments
152+
include_path_list = [os.path.join(cuda_path, "include")]
399153
include_dirs = [
400154
os.path.dirname(sysconfig.get_path("include")),
401155
] + include_path_list
@@ -442,21 +196,26 @@ def _cleanup_dst_files():
442196

443197
# Build extension list
444198
extensions = []
445-
static_runtime_libraries = ["cudart_static", "rt"] if sys.platform == "linux" else ["cudart_static"]
446199
cuda_bindings_files = glob.glob("cuda/bindings/*.pyx")
447200
if sys.platform == "win32":
448201
cuda_bindings_files = [f for f in cuda_bindings_files if "cufile" not in f]
202+
203+
def get_static_libraries(f):
204+
if os.path.basename(f) in ("runtime.pyx", "runtime_ptds.pyx"):
205+
if sys.platform == "linux":
206+
return ["cudart_static", "rt"]
207+
else:
208+
return ["cudart_static"]
209+
return None
210+
449211
sources_list = [
450-
# private
451-
(["cuda/bindings/_bindings/cyruntime.pyx"], static_runtime_libraries),
452-
(["cuda/bindings/_bindings/cyruntime_ptds.pyx"], static_runtime_libraries),
453212
# utils
454213
(["cuda/bindings/utils/*.pyx"], None),
455214
# public
456215
*(([f], None) for f in cuda_bindings_files),
457216
# internal files used by generated bindings
458217
(["cuda/bindings/_internal/utils.pyx"], None),
459-
*(([f], None) for f in dst_files if f.endswith(".pyx")),
218+
*(([f], get_static_libraries(f)) for f in dst_files if f.endswith(".pyx")),
460219
]
461220

462221
for sources, libraries in sources_list:

cuda_bindings/cuda/bindings/_bindings/__init__.py

Whitespace-only changes.

0 commit comments

Comments
 (0)