From 115205e2d283d6f7f9c290710073fbebcdfa2627 Mon Sep 17 00:00:00 2001 From: Aryan Date: Wed, 5 Aug 2026 21:46:14 -0400 Subject: [PATCH 1/3] cuda.core: accept ProgramOptions(name=None) ProgramOptions.name is annotated str | None, but __post_init__ called .encode() on it unconditionally, so passing None raised AttributeError before any CUDA call was reached. Normalize None to the documented default, matching how arch is handled in the same method. The encoded value is identical to the existing default path, so the bytes passed to nvrtcCreateProgram are unchanged. Signed-off-by: Aryan --- cuda_core/cuda/core/_program.pyi | 4 +++- cuda_core/cuda/core/_program.pyx | 9 ++++++++- cuda_core/tests/test_program.py | 10 ++++++++++ 3 files changed, 21 insertions(+), 2 deletions(-) diff --git a/cuda_core/cuda/core/_program.pyi b/cuda_core/cuda/core/_program.pyi index df7ed66446a..c615ea821be 100644 --- a/cuda_core/cuda/core/_program.pyi +++ b/cuda_core/cuda/core/_program.pyi @@ -145,6 +145,7 @@ class ProgramOptions: ---------- name : str, optional Name of the program. If the compilation succeeds, the name is passed down to the generated :class:`ObjectCode`. + When set to `None`, ``"default_program"`` is used. arch : str, optional Pass the SM architecture value, such as ``sm_`` (for generating CUBIN) or ``compute_`` (for generating PTX). If not provided, the current device's architecture @@ -313,7 +314,7 @@ class ProgramOptions: math builtins library. Only supported for the NVVM backend. Default: False """ - name: str | None = 'default_program' + name: str | None = _DEFAULT_PROGRAM_NAME arch: str | None = None relocatable_device_code: bool | None = None extensible_whole_program: bool | None = None @@ -417,6 +418,7 @@ class ProgramOptions: """Convert extra_sources to bytes format for NVVM.""" __all__ = ['Program', 'ProgramOptions'] ProgramHandleT = nvrtc.nvrtcProgram | int | LinkerHandleT +_DEFAULT_PROGRAM_NAME = 'default_program' _nvvm_module = None _nvvm_import_attempted = False diff --git a/cuda_core/cuda/core/_program.pyx b/cuda_core/cuda/core/_program.pyx index 27b1e5aa914..37bff0b0077 100644 --- a/cuda_core/cuda/core/_program.pyx +++ b/cuda_core/cuda/core/_program.pyx @@ -290,6 +290,9 @@ cdef class Program: # ============================================================================= +_DEFAULT_PROGRAM_NAME = "default_program" + + @dataclass class ProgramOptions: """Customizable options for configuring :class:`Program`. @@ -298,6 +301,7 @@ class ProgramOptions: ---------- name : str, optional Name of the program. If the compilation succeeds, the name is passed down to the generated :class:`ObjectCode`. + When set to `None`, ``"default_program"`` is used. arch : str, optional Pass the SM architecture value, such as ``sm_`` (for generating CUBIN) or ``compute_`` (for generating PTX). If not provided, the current device's architecture @@ -467,7 +471,7 @@ class ProgramOptions: Default: False """ - name: str | None = "default_program" + name: str | None = _DEFAULT_PROGRAM_NAME arch: str | None = None relocatable_device_code: bool | None = None extensible_whole_program: bool | None = None @@ -523,6 +527,9 @@ class ProgramOptions: numba_debug: bool | None = None # Custom option for Numba debugging def __post_init__(self) -> None: + # Set name to default if not provided + if self.name is None: + self.name = _DEFAULT_PROGRAM_NAME self._name = self.name.encode() # Set arch to default if not provided if self.arch is None: diff --git a/cuda_core/tests/test_program.py b/cuda_core/tests/test_program.py index a9dc4966346..28465425c0e 100644 --- a/cuda_core/tests/test_program.py +++ b/cuda_core/tests/test_program.py @@ -351,6 +351,16 @@ def test_program_init_invalid_code_format(): Program(code, "c++") +# arch is passed explicitly so the current device is not queried. +@pytest.mark.agent_authored(model="claude-opus-5") +@pytest.mark.parametrize("name", [None, "my_program"]) +def test_program_options_name_accepts_none(name): + options = ProgramOptions(name=name, arch="sm_90") + expected = "default_program" if name is None else name + assert options.name == expected + assert options._name == expected.encode() + + # This is tested against the current device's arch def test_program_compile_valid_target_type(init_cuda): code = 'extern "C" __global__ void my_kernel() {}' From 5b27f809a44df46a482c5bbb0dcd66224d66c226 Mon Sep 17 00:00:00 2001 From: Aryan Date: Wed, 5 Aug 2026 21:54:18 -0400 Subject: [PATCH 2/3] cuda.core: cover name=None through compile and add a release note Extend coverage past ProgramOptions construction to assert the normalized name reaches ObjectCode.name, matching the shape of test_program_compile_valid_target_type. Signed-off-by: Aryan --- cuda_core/docs/source/release/1.2.0-notes.rst | 6 ++++++ cuda_core/tests/test_program.py | 8 ++++++++ 2 files changed, 14 insertions(+) diff --git a/cuda_core/docs/source/release/1.2.0-notes.rst b/cuda_core/docs/source/release/1.2.0-notes.rst index 4622d837c35..14e14a83ee4 100644 --- a/cuda_core/docs/source/release/1.2.0-notes.rst +++ b/cuda_core/docs/source/release/1.2.0-notes.rst @@ -45,6 +45,12 @@ Fixes and enhancements closes `#2408 `__) >>>>>>> origin/main +- :class:`ProgramOptions` now accepts ``name=None`` and falls back to the + documented default ``"default_program"``. Previously the annotated and + documented ``None`` raised ``AttributeError`` during construction. + (`#2517 `__, + closes `#2516 `__) + Deprecation Notices ------------------- diff --git a/cuda_core/tests/test_program.py b/cuda_core/tests/test_program.py index 28465425c0e..00cf9cb5dd0 100644 --- a/cuda_core/tests/test_program.py +++ b/cuda_core/tests/test_program.py @@ -361,6 +361,14 @@ def test_program_options_name_accepts_none(name): assert options._name == expected.encode() +@pytest.mark.agent_authored(model="claude-opus-5") +def test_program_name_none_reaches_object_code(init_cuda): + code = 'extern "C" __global__ void my_kernel() {}' + program = Program(code, "c++", options={"name": None}) + ptx_object_code = program.compile("ptx") + assert ptx_object_code.name == "default_program" + + # This is tested against the current device's arch def test_program_compile_valid_target_type(init_cuda): code = 'extern "C" __global__ void my_kernel() {}' From c882579be43ea94aae2147d258b6b7fc5fc47e16 Mon Sep 17 00:00:00 2001 From: Aryan Date: Wed, 5 Aug 2026 21:57:41 -0400 Subject: [PATCH 3/3] cuda.core: drop the redundant compile-level test and the name constant ObjectCode.name receives an already-normalized options.name, so the compile-level assertion could not fail independently of the options test. Inline the default literal instead of a module constant, which kept a private symbol out of the generated stub. Signed-off-by: Aryan --- cuda_core/cuda/core/_program.pyi | 3 +-- cuda_core/cuda/core/_program.pyx | 7 ++----- cuda_core/tests/test_program.py | 8 -------- 3 files changed, 3 insertions(+), 15 deletions(-) diff --git a/cuda_core/cuda/core/_program.pyi b/cuda_core/cuda/core/_program.pyi index c615ea821be..d046523b007 100644 --- a/cuda_core/cuda/core/_program.pyi +++ b/cuda_core/cuda/core/_program.pyi @@ -314,7 +314,7 @@ class ProgramOptions: math builtins library. Only supported for the NVVM backend. Default: False """ - name: str | None = _DEFAULT_PROGRAM_NAME + name: str | None = 'default_program' arch: str | None = None relocatable_device_code: bool | None = None extensible_whole_program: bool | None = None @@ -418,7 +418,6 @@ class ProgramOptions: """Convert extra_sources to bytes format for NVVM.""" __all__ = ['Program', 'ProgramOptions'] ProgramHandleT = nvrtc.nvrtcProgram | int | LinkerHandleT -_DEFAULT_PROGRAM_NAME = 'default_program' _nvvm_module = None _nvvm_import_attempted = False diff --git a/cuda_core/cuda/core/_program.pyx b/cuda_core/cuda/core/_program.pyx index 37bff0b0077..7fb099b06d2 100644 --- a/cuda_core/cuda/core/_program.pyx +++ b/cuda_core/cuda/core/_program.pyx @@ -290,9 +290,6 @@ cdef class Program: # ============================================================================= -_DEFAULT_PROGRAM_NAME = "default_program" - - @dataclass class ProgramOptions: """Customizable options for configuring :class:`Program`. @@ -471,7 +468,7 @@ class ProgramOptions: Default: False """ - name: str | None = _DEFAULT_PROGRAM_NAME + name: str | None = "default_program" arch: str | None = None relocatable_device_code: bool | None = None extensible_whole_program: bool | None = None @@ -529,7 +526,7 @@ class ProgramOptions: def __post_init__(self) -> None: # Set name to default if not provided if self.name is None: - self.name = _DEFAULT_PROGRAM_NAME + self.name = "default_program" self._name = self.name.encode() # Set arch to default if not provided if self.arch is None: diff --git a/cuda_core/tests/test_program.py b/cuda_core/tests/test_program.py index 00cf9cb5dd0..28465425c0e 100644 --- a/cuda_core/tests/test_program.py +++ b/cuda_core/tests/test_program.py @@ -361,14 +361,6 @@ def test_program_options_name_accepts_none(name): assert options._name == expected.encode() -@pytest.mark.agent_authored(model="claude-opus-5") -def test_program_name_none_reaches_object_code(init_cuda): - code = 'extern "C" __global__ void my_kernel() {}' - program = Program(code, "c++", options={"name": None}) - ptx_object_code = program.compile("ptx") - assert ptx_object_code.name == "default_program" - - # This is tested against the current device's arch def test_program_compile_valid_target_type(init_cuda): code = 'extern "C" __global__ void my_kernel() {}'