Skip to content

Add automatically-generated .pyi files to cuda_core#2061

Open
mdboom wants to merge 7 commits into
NVIDIA:mainfrom
mdboom:pyi-experiment
Open

Add automatically-generated .pyi files to cuda_core#2061
mdboom wants to merge 7 commits into
NVIDIA:mainfrom
mdboom:pyi-experiment

Conversation

@mdboom
Copy link
Copy Markdown
Contributor

@mdboom mdboom commented May 11, 2026

This adds automatically-generated .pyi files to cuda_core. This allows IDE auto-completion to work (which is also used by IDE-integrated coding agents). This has also found 2 real bugs in our code already (#2049 and #2050). The ability to catch a certain class of bugs with this will be really helpful going forward, especially since our linting abilities with cython-lint are a bit behind what they are in pure Python.

(Marked as a draft because it requires a small patch to stubgen-pyx and is currently using my personal fork. We can update this after the patch, or something like it, is merged -- jon-edward/stubgen-pyx#19)

Reviewing

Three separate commits to make this easier to review:

  • Infrastructure / configuration updates to integrate the tools
  • Updates to the type annotations to make everything consistent and pass type-checking
  • Adding all of the generated .pyi files

Workflow

The workflow I chose here is that .pyi files are committed explicitly into the repo. A pre-commit hook regenerates them and then runs a type checker over the entire result.

Alternative: An alternative approach would be to not store them in the repo: generate them at the time of wheel building, and then test them as part of the test suite.

I prefer the approach taken here, because it will make it obvious in a PR when our public API has changed, and we can validate that it is making an allowed transformation (adding, but not removing etc.) We could (as a future step), use something like griffe.

Type-checking

This adds mypy-based type checking as part of the pre-commit hook. I ran this code over all of the major type checkers (mypy, pyright, pyrefly and ty). mypy and pyright were very similar in behavior, we could use either, but given that pathfinder is already using mypy and I have more confidence in its long-term viability, I went with that. pyrefly seems worth following up on in a year, and ty was pretty limited at this point. It takes 0.25s to run mypy over cuda-core on my machine, so I don't think moving to a Rust-based checker matters much at this point.

Assessment

Overall, I was very impressed by how far stubgen-pyx has come since last I looked, and I think it's good enough to be relied on at this point.

One minor rough edge is with code at the top-level. Code that calls cdef functions at the top-level will not type check/resolve, because (correctly) those cdef functions are not exposed to the .pyi file. The solution I used here was just to put these blocks of code at the top level into functions and call them, which is probably not a bad practice in terms of global namespace hygiene anyway.

The biggest shortcoming I see with this approach is that the line numbers in the .pyi files don't map back to the original line numbers in the .pyx or .pxi files. So when mypy displays a type error, you have to weave through and find the original source to edit it. (This is sort of what the #line directive in the C preprocessor is for -- I don't think .pyi files have an equivalent). I think we can live with that, but it's a bit of a pain point. All that said, my agent had no trouble realizing it had to go back and make changes to the .pyx file, not directly in the .pyi file.

Cc: @jon-edward (for viz)

@mdboom mdboom self-assigned this May 11, 2026
@mdboom mdboom added enhancement Any code-related improvements cuda.core Everything related to the cuda.core module labels May 11, 2026
@copy-pr-bot
Copy link
Copy Markdown
Contributor

copy-pr-bot Bot commented May 11, 2026

Auto-sync is disabled for draft pull requests in this repository. Workflows must be run manually.

Contributors can view more details about this message here.

@leofang
Copy link
Copy Markdown
Member

leofang commented May 11, 2026

cc @ZzEeKkAa for vis

@jon-edward
Copy link
Copy Markdown

Hello! The patch that @mdboom mentioned is merged, and is included in stubgen-pyx v0.2.6 which is now available via pip

@mdboom mdboom requested a review from Andy-Jost May 12, 2026 13:58
@mdboom mdboom marked this pull request as ready for review May 12, 2026 17:30
@mdboom mdboom closed this May 12, 2026
@mdboom mdboom reopened this May 12, 2026
@github-actions
Copy link
Copy Markdown

@mdboom
Copy link
Copy Markdown
Contributor Author

mdboom commented May 12, 2026

pre-commit.ci autofix

@copy-pr-bot
Copy link
Copy Markdown
Contributor

copy-pr-bot Bot commented May 12, 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.

Copy link
Copy Markdown
Contributor

@Andy-Jost Andy-Jost left a comment

Choose a reason for hiding this comment

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

No issues here. I checked the .pyx files carefully but didn't read the generated .pyi files.

Comment on lines +44 to +45
if TYPE_CHECKING:
from cuda.core.graph import GraphBuilder
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.

Previously @leofang mentioned he wanted to remove all if TYPE_CHECKING blocks (but I don't recall the reason). Is that no longer the guidance? It appears real imports are needed by the type checking tools.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Yes. Even if you want to use the quoted syntax, i.e. def my_function() -> "GraphBuilder":, you need to import those things so the type checker knows what you are referring to. The if TYPE_CHECKING: block is just a way to prevent cyclical imports that this otherwise might cause.

from __future__ import annotations

from collections.abc import Callable, Iterable, MutableSet
from collections.abc import Callable, Iterable, MutableSet, Set as AbstractSet
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.

Does the distinct name AbstractSet buy anything?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Probably not. The agent did this. On reflection it's probably clearer to not rename. I can update the PR.

Copy link
Copy Markdown
Contributor Author

@mdboom mdboom May 12, 2026

Choose a reason for hiding this comment

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

I think the agent did this because it's the convention in typeshed:

https://github.com/search?q=repo%3Apython%2Ftypeshed%20abstractset&type=code

Not an argument that we need go either way. It's functionality the same thing, and personally I don't like to rename things unless it's to workaround a clash or something.

Comment on lines -338 to +348
buf._size = new_size
# TODO: #2049 This is a real bug, accessing _size which doesn't exist.
# Fix bug and remove the "type: ignore[attr-defined]" comment.
buf._size = new_size # type: ignore[attr-defined]
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.

This seems like a good enough reason to Cythonize even when we don't expect performance benefits.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Or, conversely, it's a reason not to Cythonize because the type checker would not have been able to catch this bug if it were Cython 😄

But it's all tradeoffs. We tend to take runtime performance over developer productivity in this project, which is reasonable.

Comment thread cuda_core/cuda/core/_memory/_virtual_memory_resource.py Outdated
# `from cuda import cuda as driver` etc. import path.)
# `as X` form is the PEP 484 explicit re-export marker, which type checkers
# need to treat these names as part of the public API of this module.
from cuda.bindings import driver as driver, nvrtc as nvrtc, runtime as runtime
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Question for @leofang: Do we still need the fallback to the pre-cuda-bindings library?

from cuda import cuda as driver
from cuda import cudart as runtime
from cuda import nvrtc

It makes the type-checking a lot more complicated. There is probably a solution, but I didn't want to add it if we can just drop this because cuda_bindings has been out long enough by now.

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

Labels

cuda.core Everything related to the cuda.core module enhancement Any code-related improvements

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants