Skip to content

cuda.core: Replace string literals with enums in public API #1995

Closed

Description

@mdboom

This is just a result of a sweep for using strings where enums could be used. This is just to aid the discussion about whether they should be addressed.

Several places in the cuda.core public API accept or return string values chosen from a small, fixed vocabulary. These are candidates for replacement with proper Python enum types, which would improve discoverability, IDE autocompletion, isinstance checking, and catch typos at the call site rather than at runtime.


Findings

Compilation / Linking pipeline

Location Kind Values
Program.__init__(code_type) arg "c++", "ptx", "nvvm"
Program.compile(target_type) arg "ptx", "cubin", "ltoir"
Program.backend return "NVRTC", "NVVM", "nvJitLink", "driver"
Program.pch_status return "created", "not_attempted", "failed"
Linker.link(target_type) arg "cubin", "ptx"
Linker.backend return "nvJitLink", "driver"
ObjectCode.code_type return "cubin", "ptx", "ltoir", "fatbin", "object", "library"

Memory

Location Kind Values
ManagedMemoryResourceOptions.preferred_location_type arg "device", "host", "host_numa"
ManagedMemoryResource.preferred_location return (tuple[0]) "device", "host", "host_numa"
VirtualMemoryResourceOptions.allocation_type arg "pinned", "managed"
VirtualMemoryResourceOptions.location_type arg "device", "host", "host_numa", "host_numa_current"
VirtualMemoryResourceOptions.handle_type arg "posix_fd", "generic", "win32_kmt", "fabric"
VirtualMemoryResourceOptions.granularity arg "recommended", "minimum"
VirtualMemoryResourceOptions.self_access / peer_access arg "rw", "r"

Note: the VirtualMemoryResourceOptions fields already use Literal[...] type hints, which partially addresses the issue, but proper enums would still improve iteration, isinstance checks, and user discoverability.

Graph API

Location Kind Values
GraphAllocOptions.memory_type arg "device", "host", "managed"
AllocNode.memory_type return "device", "host", "managed"
ConditionalNode.cond_type return "if", "while", "switch"

Proposed enum groupings

Enum name Values Consumers
CodeType cubin, ptx, ltoir, fatbin, object, library ObjectCode.code_type, Linker.link, Program.compile
SourceType c++, ptx, nvvm Program.__init__
CompilerBackend NVRTC, NVVM, nvJitLink, driver Program.backend, Linker.backend
PchStatus created, not_attempted, failed Program.pch_status
MemoryType device, host, managed GraphAllocOptions, AllocNode, ManagedMemoryResource
LocationType device, host, host_numa, host_numa_current VirtualMemoryResourceOptions, ManagedMemoryResourceOptions
ConditionalType if_, while_, switch_ ConditionalNode.cond_type
HandleType posix_fd, generic, win32_kmt, fabric VirtualMemoryResourceOptions
Granularity recommended, minimum VirtualMemoryResourceOptions
AccessMode rw, r VirtualMemoryResourceOptions

Discussion points

  • The compilation/linking pipeline (Program, Linker, ObjectCode) is the highest-impact area since string values flow across multiple API boundaries.
  • Switching from str to enum would be a breaking change for existing callers. One migration path is to accept both str and the enum type during a deprecation window.
  • Some values ("c++", "if", "while") are not valid Python identifiers and would need renaming (e.g. SourceType.CPP, ConditionalType.IF).

Activity

  1. added
    enhancementAny code-related improvements
    RFCPlans and announcements
    cuda.coreEverything related to the cuda.core module
    on Apr 30, 2026
  2. mdboom commented on Apr 30, 2026

    @mdboom
    ContributorAuthor

    Note that the above list is just where strings are used where enums could be, not the opposite, where we use enums but strings are not accepted. We could also produce that list.

    My proposed solution:

    • Everywhere we accept something "enum like", the type is SpecificEnum | str. (SpecificEnum | Literal[...] would be better from a type perspective, but IMHO is not worth the repeating yourself burden).
    • Everywhere we return something "enum like", we return an enum, and str(enum) gives a string.

    This seems to address type checking much better than what we have today, and also reduces a class of errors where it is easier to mis-spell a string than an enum value.

    It also gives users a fast path (using enums) that using strings may not have.

  3. added this to the cuda.core v1.0.0 milestone on May 1, 2026
  4. mdboom commented on May 1, 2026

    @mdboom
    ContributorAuthor

    From a user/API perspective, I think consensus from the meeting is that it would be ideal to support both strings and enums, i.e.

    call_func(MyEnum.VALUE)
    get_result() == MyEnum.VALUE
    

    and

    call_func("value")
    get_result() == "value"
    

    would be equivalent and supported everywhere "logically enum-like things" would be needed.

    It is desirable to give type checkers and linters as much as they can to deal with here. Much of that will be theoretical today, since we don't have .pyi files, but we should plan for a future where we do.

    We have two sets of enums in cuda-core: (A) those that are internal to cuda-core and exist only for the Pythonic layer and (B) those that are re-exposures of C-style int enums from CUDA libraries (usually through cuda.bindings). The enum implementation does not need to be the same in both cases (it's an implementation detail), but having a consistent interface across all of them seems desirable (especially whether they implicitly convert to/from strings), so the user doesn't have to know which type of enum they are dealing with.

    After doing a bunch of benchmarking of alternatives, I've reached the following conclusion:

    (A) Existing cuda-core enums

    The stdlib enum.StrEnum does not have the performance issues of enum.IntEnum, and is every bit as fast as using strings. For the existing cuda-core enum-like-things using strings that don't need to map directly to C (int) enums, this is the right choice. It will allow both strings or StrEnum at all of our call sites with basically zero overhead. Making this change, our users can decide whether they prefer enums or strings and the type checking vs. convenience tradeoffs.

    Extending FastEnum slightly to use them for the above purpose adds about 40ns of overhead vs. StrEnum. Unlike FastEnum, StrEnum values do not have docstrings, so the doc pages can only list their values, not add explanations of what each value means. I think we treat this as a doc build problem: it would better to extend/fix Sphinx somehow to make that work than pay a runtime penalty just to have good docs. 🙃

    (B) Re-exposed C-style enums

    These are currently using FastEnum, since they are integer values. These do not currently implicitly convert to/from strings, but can be easily extended to do so. The performance penalty of doing that accepting either a string or a FastEnum at each call site is around 40ns. I think it is worth that to have a consistent interface for all of our enums across cuda-core.

    Urgency?

    All of this is a little complex and risky to get squeezed in right before the 1.0 release. So what happens if we don't and instead do it later?

    Fundamentally, we are /extending/ the set of values our functions can accept, which is not API breaking.

    For argument types of type (A), we would be going from str to str | Enum (or in some cases we have Literal["a", "b", "c"] which would go to str | Enum). Type (B) would go from Enum to Enum | str.

    For returning values, we would be going from str to Enum, and we would have to ensure that result == "value" would continue to work. If we need to retain isinstance(result, str) or result is "value", that may not be possible. That seems fine to me, barring XKCD 1172.

    What we couldn't do later (easily) is decide that we want to remove uses of strings and use only enums everywhere (or conversely for type (B) accept only strings everywhere).

    I think I'm ok to commit to that much and not try to rush this through (though I think it is generally a good idea to do this).

  5. mdboom commented on May 1, 2026

    @mdboom
    ContributorAuthor

    I think we treat this as a doc build problem: it would better to extend/fix Sphinx somehow to make that work than pay a runtime penalty just to have good docs.

    FWIW: It's ugly but this works:

    class SourceType(StrEnum):
        CXX = "c++"
        PTX = "ptx"
        NVVM = "nvvm"
    
    SourceType.CXX.__doc__ = "C++ source code."
    
  6. added a commit that references this issue on May 5, 2026
    4d1ddb4
  7. added a commit that references this issue on May 5, 2026
    cec0efb
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

P0High priority - Must do!RFCPlans and announcementscuda.coreEverything related to the cuda.core moduleenhancementAny code-related improvements

Type

No type

Projects

No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions