Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/workflows/ci-pixi-source-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,10 @@ on:
- "**/pixi.toml"
- "**/pixi.lock"
- "cuda_bindings/build_hooks.py"
- "cuda_bindings/pyproject.toml"
- "cuda_core/build_hooks.py"
- "cuda_core/CMakeLists.txt"
- "cuda_core/pyproject.toml"
- "cuda_bindings/cuda/bindings/**" # generated bindings sources
- "cuda_bindings/tests/cython/**"
- "cuda_core/tests/cython/**"
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/test-sdist-linux.yml
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ jobs:
printf 'cuda-bindings @ %s\n' "${bindings_uri}"
} | tee wheel-constraints/cuda-core.txt

# cuda_core sdist delegates to setuptools (no CTK needed), but
# cuda_core sdist delegates directly to scikit-build-core (no CTK needed), but
# wheel-from-sdist needs CTK and cuda-bindings (dynamic build dep via
# get_requires_for_build_wheel in build_hooks.py).
- name: Build cuda.core sdist and wheel-from-sdist
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/test-sdist-windows.yml
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ jobs:
printf 'cuda-bindings @ %s\n' "${bindings_uri}"
} | tee wheel-constraints/cuda-core.txt

# cuda_core sdist delegates to setuptools (no CTK needed), but
# cuda_core sdist delegates directly to scikit-build-core (no CTK needed), but
# wheel-from-sdist needs CTK and cuda-bindings (dynamic build dep via
# get_requires_for_build_wheel in build_hooks.py).
- name: Build cuda.core sdist and wheel-from-sdist
Expand Down
1 change: 1 addition & 0 deletions cuda_bindings/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ include = ["cuda*"]

[tool.setuptools]
include-package-data = false
package-dir = {"" = "."}

[tool.setuptools.package-data]
"*" = ["*.pxd", "*.pxi"]
Expand Down
52 changes: 52 additions & 0 deletions cuda_bindings/tests/test_build_hooks.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0

"""Tests for the cuda-bindings PEP 517 build wrapper."""

import importlib.util
import zipfile
from pathlib import Path
from unittest import mock

import pytest


def _load_build_hooks():
build_hooks_path = Path(__file__).parent.parent / "build_hooks.py"
spec = importlib.util.spec_from_file_location("_cuda_bindings_test_build_hooks", build_hooks_path)
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
return module


build_hooks = _load_build_hooks()


@pytest.mark.agent_authored(model="gpt-5.6")
def test_editable_wheel_uses_data_only_source_root_pth(tmp_path, monkeypatch):
project_root = Path(build_hooks.__file__).resolve().parent
bindings_build = mock.Mock()
monkeypatch.chdir(project_root)
monkeypatch.setattr(build_hooks, "_build_cuda_bindings", bindings_build)

wheel_name = build_hooks.build_editable(tmp_path, {"debug": False})

bindings_build.assert_called_once_with(debug=False)
with zipfile.ZipFile(tmp_path / wheel_name) as wheel:
pth_files = [name for name in wheel.namelist() if name.endswith(".pth")]
assert len(pth_files) == 1
assert wheel.read(pth_files[0]).decode("utf-8") == f"{project_root}\n"


@pytest.mark.agent_authored(model="gpt-5.6")
def test_regular_wheel_does_not_include_editable_path(tmp_path, monkeypatch):
project_root = Path(build_hooks.__file__).resolve().parent
bindings_build = mock.Mock()
monkeypatch.chdir(project_root)
monkeypatch.setattr(build_hooks, "_build_cuda_bindings", bindings_build)

wheel_name = build_hooks.build_wheel(tmp_path, {"debug": False})

bindings_build.assert_called_once_with(debug=False)
with zipfile.ZipFile(tmp_path / wheel_name) as wheel:
assert not any(name.endswith(".pth") for name in wheel.namelist())
3 changes: 2 additions & 1 deletion cuda_core/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ This file describes `cuda_core`, the high-level Pythonic CUDA subpackage in the
- execution path: `_launcher.pyx`, `_launch_config.pyx`, `_stream.pyx`
- **C++ helpers**: module-specific C++ implementations live under
`cuda/core/_cpp/`.
- **Build backend**: `build_hooks.py` handles Cython extension setup and build
- **Build backend**: `CMakeLists.txt` defines the Cython extension build;
`build_hooks.py` wraps scikit-build-core to provide CUDA-major-specific build
dependency wiring.

## Build and version coupling
Expand Down
216 changes: 216 additions & 0 deletions cuda_core/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,216 @@
# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
#
# SPDX-License-Identifier: Apache-2.0

cmake_minimum_required(VERSION 3.21...4.4)

project(cuda_core LANGUAGES CXX)

find_package(
Python
COMPONENTS Interpreter Development.Module
REQUIRED
)
find_package(Cython 3.2.5...<3.3 MODULE REQUIRED)
include(UseCython)

option(CUDA_PYTHON_COVERAGE "Build Cython line tracing support" "$ENV{CUDA_PYTHON_COVERAGE}")

if(DEFINED ENV{CUDA_PATH} AND NOT "$ENV{CUDA_PATH}" STREQUAL "")
set(_cuda_core_cuda_root "$ENV{CUDA_PATH}")
elseif(DEFINED ENV{CUDA_HOME} AND NOT "$ENV{CUDA_HOME}" STREQUAL "")
set(_cuda_core_cuda_root "$ENV{CUDA_HOME}")
endif()
if(NOT _cuda_core_cuda_root)
message(FATAL_ERROR "CUDA_PATH or CUDA_HOME must point to a CUDA Toolkit installation")
endif()

if(DEFINED ENV{CUDA_CORE_BUILD_MAJOR} AND NOT "$ENV{CUDA_CORE_BUILD_MAJOR}" STREQUAL "")
set(_cuda_core_build_major "$ENV{CUDA_CORE_BUILD_MAJOR}")
endif()

cmake_path(ABSOLUTE_PATH _cuda_core_cuda_root NORMALIZE)
set(_cuda_header "${_cuda_core_cuda_root}/include/cuda.h")
if(NOT EXISTS "${_cuda_header}")
message(FATAL_ERROR "CUDA header not found: ${_cuda_header}")
endif()

file(STRINGS "${_cuda_header}" _cuda_version_line REGEX "^#[ \t]*define[ \t]+CUDA_VERSION[ \t]+[0-9]+[ \t]*$")
if(NOT _cuda_version_line)
message(FATAL_ERROR "Cannot determine CUDA major version from ${_cuda_header}")
endif()
string(REGEX MATCH "[0-9]+" _cuda_version "${_cuda_version_line}")
math(EXPR _cuda_header_major "${_cuda_version} / 1000")

if("${_cuda_core_build_major}" STREQUAL "")
set(_cuda_core_build_major "${_cuda_header_major}")
elseif(NOT _cuda_core_build_major MATCHES "^[0-9]+$")
message(FATAL_ERROR "CUDA_CORE_BUILD_MAJOR must be an integer, got ${_cuda_core_build_major}")
elseif(NOT "${_cuda_core_build_major}" STREQUAL "${_cuda_header_major}")
message(
FATAL_ERROR
"CUDA_CORE_BUILD_MAJOR=${_cuda_core_build_major} does not match CUDA ${_cuda_header_major} "
"reported by ${_cuda_header}"
)
endif()
message(STATUS "Building cuda-core for CUDA ${_cuda_core_build_major} using ${_cuda_core_cuda_root}")

if(WIN32)
execute_process(
COMMAND
"${Python_EXECUTABLE}" -c
"import sysconfig; print(int(bool(sysconfig.get_config_var('Py_GIL_DISABLED'))))"
RESULT_VARIABLE _python_freethreaded_result
OUTPUT_VARIABLE _python_freethreaded
ERROR_VARIABLE _python_freethreaded_error
OUTPUT_STRIP_TRAILING_WHITESPACE
)
if(NOT _python_freethreaded_result STREQUAL "0")
message(FATAL_ERROR "Could not determine whether Python is free-threaded.\n${_python_freethreaded_error}")
endif()
endif()

if(CUDA_PYTHON_COVERAGE)
set(_coverage_directive -X linetrace=True)
endif()

if(WIN32 AND CMAKE_BUILD_TYPE STREQUAL "Debug")
message(FATAL_ERROR "Debuggable builds are not supported on Windows")
endif()

set(_cuda_core_source_root "${CMAKE_CURRENT_SOURCE_DIR}/cuda/core")
set(_cython_common_args
-Werror
-X embedsignature=True
-X warn.deprecated.IF=False
-X freethreading_compatible=True
-E CUDA_CORE_BUILD_MAJOR=${_cuda_core_build_major}
-I "${CMAKE_CURRENT_SOURCE_DIR}"
${_coverage_directive}
)

if(CMAKE_BUILD_TYPE STREQUAL "Debug" AND NOT WIN32)
list(APPEND _cython_common_args --gdb)
endif()

add_library(cuda_core_extension_settings INTERFACE)
target_include_directories(
cuda_core_extension_settings
INTERFACE
"${_cuda_core_source_root}"
"${_cuda_core_source_root}/_include"
"${_cuda_core_cuda_root}/include"
)
target_compile_definitions(
cuda_core_extension_settings
INTERFACE
"$<$<AND:$<CONFIG:Debug>,$<PLATFORM_ID:Linux>,$<CXX_COMPILER_ID:GNU,Clang>>:_GLIBCXX_ASSERTIONS>"
)
if(CUDA_PYTHON_COVERAGE)
target_compile_definitions(
cuda_core_extension_settings
INTERFACE
CYTHON_TRACE_NOGIL=1
CYTHON_USE_SYS_MONITORING=0
)
endif()
if(WIN32 AND _python_freethreaded)
# Windows shares Python headers between GIL and free-threaded builds, so
# extension builds must supply the ABI selection macro themselves.
target_compile_definitions(cuda_core_extension_settings INTERFACE Py_GIL_DISABLED=1)
endif()

file(
GLOB_RECURSE _cuda_core_modules
CONFIGURE_DEPENDS
RELATIVE "${_cuda_core_source_root}"
"${_cuda_core_source_root}/*.pyx"
)
list(TRANSFORM _cuda_core_modules REPLACE "\\.pyx$" "")

if(MSVC)
set(_aoti_shim_def "${_cuda_core_source_root}/_include/aoti_shim.def")
set(_aoti_shim_lib "${CMAKE_CURRENT_BINARY_DIR}/aoti_shim.lib")
set(_aoti_shim_exp "${CMAKE_CURRENT_BINARY_DIR}/aoti_shim.exp")
if(CMAKE_GENERATOR_PLATFORM MATCHES "^(ARM64|arm64)$" OR CMAKE_SYSTEM_PROCESSOR MATCHES "^(ARM64|arm64|aarch64)$")
set(_aoti_machine ARM64)
else()
set(_aoti_machine X64)
endif()
add_custom_command(
OUTPUT "${_aoti_shim_lib}"
BYPRODUCTS "${_aoti_shim_exp}"
COMMAND
"${CMAKE_AR}"
"/DEF:${_aoti_shim_def}"
"/OUT:${_aoti_shim_lib}"
"/MACHINE:${_aoti_machine}"
DEPENDS "${_aoti_shim_def}"
COMMENT "Generating the AOTI shim import library"
VERBATIM
)
add_custom_target(cuda_core_aoti_shim_generation DEPENDS "${_aoti_shim_lib}")
add_library(cuda_core_aoti_shim SHARED IMPORTED)
set_target_properties(cuda_core_aoti_shim PROPERTIES IMPORTED_IMPLIB "${_aoti_shim_lib}")
add_dependencies(cuda_core_aoti_shim cuda_core_aoti_shim_generation)
endif()

function(cuda_core_add_cython_module module_path)
string(REPLACE "/" "." module_suffix "${module_path}")
string(REPLACE "/" "_" target_suffix "${module_path}")
set(module_name "cuda.core.${module_suffix}")
set(target_name "cuda_core${target_suffix}")
set(pyx_file "${_cuda_core_source_root}/${module_path}.pyx")
set(cython_cpp "${CMAKE_CURRENT_BINARY_DIR}/cython/${module_path}.cpp")
cmake_path(GET module_path FILENAME module_filename)
cmake_path(GET module_path PARENT_PATH module_directory)
set(install_directory "cuda/core/${module_directory}")
set(module_output_directory "${CMAKE_CURRENT_BINARY_DIR}/extensions/${module_directory}")

cython_transpile(
"${pyx_file}"
LANGUAGE CXX
OUTPUT "${cython_cpp}"
CYTHON_ARGS ${_cython_common_args} --module-name "${module_name}"
)

set(module_sources "${cython_cpp}")
if(module_path STREQUAL "_resource_handles")
list(APPEND module_sources "${_cuda_core_source_root}/_cpp/resource_handles.cpp")
elseif(module_path STREQUAL "_tensor_map")
list(APPEND module_sources "${_cuda_core_source_root}/_cpp/tensor_map.cpp")
endif()

python_add_library("${target_name}" MODULE WITH_SOABI ${module_sources})
set_target_properties(
"${target_name}"
PROPERTIES
ARCHIVE_OUTPUT_DIRECTORY "${module_output_directory}"
CXX_STANDARD 17
CXX_STANDARD_REQUIRED YES
CXX_EXTENSIONS OFF
CXX_VISIBILITY_PRESET hidden
LIBRARY_OUTPUT_DIRECTORY "${module_output_directory}"
OUTPUT_NAME "${module_filename}"
RUNTIME_OUTPUT_DIRECTORY "${module_output_directory}"
VISIBILITY_INLINES_HIDDEN YES
)
target_link_libraries("${target_name}" PRIVATE cuda_core_extension_settings)

if(CUDA_PYTHON_COVERAGE)
install(FILES "${cython_cpp}" DESTINATION "${install_directory}")
endif()

if(MSVC AND module_path STREQUAL "_tensor_bridge")
target_link_libraries("${target_name}" PRIVATE cuda_core_aoti_shim)
endif()

install(TARGETS "${target_name}" LIBRARY DESTINATION "${install_directory}" RUNTIME DESTINATION "${install_directory}")
endfunction()

foreach(module_path IN LISTS _cuda_core_modules)
if(WIN32 AND module_path STREQUAL "_utils/_wsl_locale")
continue()
endif()
cuda_core_add_cython_module("${module_path}")
endforeach()
9 changes: 0 additions & 9 deletions cuda_core/MANIFEST.in

This file was deleted.

4 changes: 2 additions & 2 deletions cuda_core/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ This subpackage adheres to the developing practices described in the parent meta

## Debugging

Pass the `pip` / `uv` configuration option `-C="debug=True"` or
`--config-settings="debug=True"` to explicitly to build debuggable binaries.
Pass the scikit-build-core configuration option `-Ccmake.build-type=Debug` to
build debuggable binaries explicitly.
Debuggable binaries are built by default for editable builds.

Debuggable builds are not supported on Windows.
Expand Down
Loading