Skip to content

Use Python properties for Cython descriptors#2056

Open
kkraus14 wants to merge 10 commits intoNVIDIA:mainfrom
kkraus14:codex/cython-python-properties
Open

Use Python properties for Cython descriptors#2056
kkraus14 wants to merge 10 commits intoNVIDIA:mainfrom
kkraus14:codex/cython-python-properties

Conversation

@kkraus14
Copy link
Copy Markdown
Collaborator

@kkraus14 kkraus14 commented May 8, 2026

Summary

  • Add a shared python_property decorator that creates real Python property descriptors inside Cython cdef class bodies.
  • Convert the known public Cython descriptors that may raise or perform CUDA/NVML work during completion to use @python_property instead of generated getset_descriptors.
  • Add regression coverage that checks those public descriptors are property instances and not getset_descriptor objects.

Testing

  • pixi run ruff check cuda_core/cuda/core/_utils/properties.py cuda_core/tests/test_cython_property_descriptors.py
  • pixi run --manifest-path cuda_core -e cu13 pytest cuda_core/tests/test_cython_property_descriptors.py (blocked locally because cuda_core/pixi.toml on this branch still has the known python_version < "3.11" package target parse error; the pixi fix is intentionally left to the separate PR)
  • commit hooks: ruff, SPDX, toml, cython-lint, and other pre-commit checks passed

@kkraus14 kkraus14 added this to the cuda.core next milestone May 8, 2026
@kkraus14 kkraus14 added the cuda.core Everything related to the cuda.core module label May 8, 2026
@leofang leofang added bug Something isn't working P0 High priority - Must do! labels May 8, 2026
@leofang leofang requested review from leofang, mdboom and shwina May 8, 2026 16:26
@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 8, 2026

Copy link
Copy Markdown
Contributor

@mdboom mdboom left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One suggestion is just a micro-op for startup time that ultimately doesn't matter.

It would be nice to simplify the testing (if possible) though. My concern is corner case syntactic constructions that might not get caught.

Comment thread cuda_core/cuda/core/_utils/properties.py Outdated
descriptor = inspect.getattr_static(cls, cython_property.name)

assert isinstance(descriptor, property)
assert not isinstance(descriptor, types.GetSetDescriptorType)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems kind of brittle to do a source-level analysis here.

Could we just iterate over all classes and assert that they don't have any GetSetDescriptorType's? It's possible I'm missing something (maybe there are implicit descriptors always that we can't remove), but that seems less complicated.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good call. I replaced the source-level parser with runtime introspection over the cuda-core modules/classes.

A literal “no GetSetDescriptorType anywhere” check still needs filtering because Cython also emits getset descriptors for non-property things like __dict__, __weakref__, and typed cdef fields such as option dataclass fields. The updated test now allows those known field descriptors, but fails on any unexpected getset descriptor exposed by cuda-core classes.

Copy link
Copy Markdown
Contributor

@mdboom mdboom May 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, thanks for humoring me. The __dict__ and __weakref__ thing doesn't bother me (that's a pretty static set), but I forgot that cdef readonly/cdef public things would be indistinguishable from Cython properties from the outside. Probably best to revert to the first revision that parsed the code. /Maybe/ the agent could do better than regexes by using the Cython parser (I've had reasonable success with that elsewhere), but we could also cross that bridge if we find the regex approach breaks on some future code.

@leofang
Copy link
Copy Markdown
Member

leofang commented May 8, 2026

All Linux-64 CI segfaulted. (Ignored linux-arm64 CIs, there seems to be a widespread outage.)

Comment on lines +21 to +34
_OPTIONAL_IMPORT_FAILURES = {"cuda.core._tensor_bridge"}

_GETSET_FIELD_ALLOWLIST = {
("cuda.core._kernel_arg_handler", "ParamHolder", "ptr"),
("cuda.core._layout", "_StridedLayout", "itemsize"),
("cuda.core._layout", "_StridedLayout", "slice_offset"),
("cuda.core._memoryview", "StridedMemoryView", "device_id"),
("cuda.core._memoryview", "StridedMemoryView", "exporting_obj"),
("cuda.core._memoryview", "StridedMemoryView", "is_device_accessible"),
("cuda.core._memoryview", "StridedMemoryView", "ptr"),
("cuda.core._memoryview", "StridedMemoryView", "readonly"),
("cuda.core._memoryview", "_StridedMemoryViewProxy", "has_dlpack"),
("cuda.core._memoryview", "_StridedMemoryViewProxy", "obj"),
}
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This still feels very brittle where I'm not too happy about this and don't want to have to maintain a list like this.

Comment on lines +60 to +68
def _is_allowed_getset_descriptor(cls, name, descriptor) -> bool:
if name in {"__dict__", "__weakref__"}:
return True
# Typed cdef fields generate getset descriptors too, but their docs follow
# this compact "field: type" form rather than a property docstring.
doc = descriptor.__doc__
if doc is not None and doc.startswith(f"{name}:"):
return True
return (cls.__module__, cls.__qualname__, name) in _GETSET_FIELD_ALLOWLIST
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This also feels quite brittle

Comment thread cuda_core/cuda/core/_memoryview.pyx Outdated
@leofang
Copy link
Copy Markdown
Member

leofang commented May 8, 2026

(Ignored linux-arm64 CIs, there seems to be a widespread outage.)

FYI, this should be restored now.

import cuda.core.graph
import cuda.core.system

pytestmark = pytest.mark.no_cuda
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this really needed...?

Copy link
Copy Markdown
Member

@leofang leofang left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The tensor bridge module is not designed to be importable casually, so it causes a test error.

Comment thread cuda_core/tests/test_cython_property_descriptors.py Outdated
Comment thread cuda_core/tests/test_cython_property_descriptors.py Outdated
Copy link
Copy Markdown
Member

@leofang leofang left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review

Approach

python_property = property is elegant — zero runtime overhead, works with mypy/pyright (as confirmed in this thread), and makes Cython cdef class properties produce real property descriptors that rlcompleter's isinstance(..., property) guard recognizes. This is the right fix.

The _layout segfault (resolved)

The CI segfaults were caused by StridedMemoryView._layout: there's both a cdef _layout field and a @property _layout on the class. With Cython's native getset_descriptor, these coexist (the cdef field is C-only, the property is Python-only). But when @python_property is used, the Python property descriptor masks the cdef field even from Cython code, causing infinite recursion (_layout getter → get_layout()self._layout_layout getter → …). The rename to _get_layout() method is the correct fix.

Issues

  1. CI failure: _tensor_bridge exclusion mismatch. The guardrail test fails in CI because the installed package uses a versioned subpackage (cuda.core.cu12._tensor_bridge), but the exclusion set only matched the unversioned name (cuda.core._tensor_bridge). I pushed a fix (6e182ef) that matches by suffix instead of exact name, so cuda.core.cu12._tensor_bridge, cuda.core.cu13._tensor_bridge, etc. are all excluded.

  2. No functional rlcompleter test. The guardrail test checks descriptor types structurally, which is good for preventing regressions. But a test that actually calls rlcompleter.Completer.complete() on affected objects and asserts no exception would be stronger end-to-end proof. For example:

    import rlcompleter
    completer = rlcompleter.Completer({"mr": mr})
    assert completer.complete("mr.", 0) is not None  # should not raise
  3. Minor: setter pattern could use a comment. The pattern used for read-write properties:

    def peer_accessible_by(self):
        ...
    def _set_peer_accessible_by(self, devices):
        ...
    peer_accessible_by = python_property(peer_accessible_by, _set_peer_accessible_by)

    …works correctly, but it's non-obvious why the more Pythonic @peer_accessible_by.setter pattern isn't used. A brief comment noting that Cython cdef class bodies don't support .setter on a python_property would help future maintainers.

Not an issue

  • Over-broad scope (converting all ~439 properties, not just the ~13 affected ones) — defensively correct, prevents future surprises. Worth the churn.
  • Error message changes ("attribute 'X' ... is not writable""can't set attribute 'X'") — correct, Python property uses different wording than getset_descriptor.
  • graph/_graph_builder.pyx is not modified — this is fine. GraphBuilder is a regular Python class (not cdef class), so its @property decorators already produce Python property descriptors that rlcompleter handles correctly.

-- Leo's bot

@leofang
Copy link
Copy Markdown
Member

leofang commented May 9, 2026

(fixing it locally)

Copy link
Copy Markdown
Member

@leofang leofang left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(Corrections merged into the review above.)

In CI, _tensor_bridge lives under a versioned subpackage
(cuda.core.cu12._tensor_bridge), but the exclusion set only matched
the unversioned name. Switch to suffix matching so that all versioned
variants are excluded.
@copy-pr-bot
Copy link
Copy Markdown
Contributor

copy-pr-bot Bot commented May 9, 2026

This pull request requires additional validation before any workflows can run on NVIDIA's runners.

Pull request vetters can view their responsibilities here.

Contributors can view more details about this message here.

In CI, _tensor_bridge lives under a versioned subpackage
(cuda.core.cu12._tensor_bridge), but the exclusion set only matched
the unversioned name. Switch to suffix matching so that all versioned
variants are excluded.
@leofang
Copy link
Copy Markdown
Member

leofang commented May 9, 2026

/ok to test 6e182ef

Python 3.10 uses "property '...' of '...' object has no setter" while
3.11+ uses "can't set attribute '...'". Relax the regex to accept both.
@leofang
Copy link
Copy Markdown
Member

leofang commented May 9, 2026

/ok to test 7c52713

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working cuda.core Everything related to the cuda.core module P0 High priority - Must do!

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants