fix(cmake): quote CUDA architecture list so wheels ship every target - #490
Merged
KemengHuang merged 2 commits intoSep 3, 2026
Merged
Conversation
…atrix
`set_target_properties(... PROPERTIES ... CUDA_ARCHITECTURES
${UIPC_CUDA_ARCHITECTURES} ...)` did not quote the variable. An unquoted
multi-element list expands into separate arguments, which breaks the
PROPERTIES key/value pairing: the property kept only the first entry and
the remaining ones were silently absorbed as bogus property names
(`80-real` became a property whose value was `86-real`).
Every published wheel up to 0.0.27 therefore shipped `sm_75` SASS only and
lost the `89-virtual` PTX fallback, even though pyproject.toml and
compatibility.json declared 75/80/86/89-real plus 89-virtual. On consumer
Blackwell (`sm_120`) that surfaces as CUDA error 500 `named symbol not
found` at `world.init(scene)`: under CUDA 12.x lazy module loading a
missing device image appears as an unregistered symbol rather than error
209 `cudaErrorNoKernelImageForDevice`. The developer default `native` is a
single element, so the defect never reproduced locally.
Quote the variable at all three sites and add `120-real` to the release
matrix so 50-series cards get native SASS instead of relying on a JIT from
`89-virtual`. `89-virtual` stays as the lowest virtual entry because PTX
only JITs upward, and it is what still covers sm_90 and sm_100.
Verified through `arch=compute_*,code=*` in compile_commands.json: all 199
CUDA translation units now receive the full list including
`code=[compute_89]`, where before only `sm_75` was emitted.
`scripts/check_release_policy.py` stays green with compatibility.json
mirrored.
XMake needs no counterpart: its architecture surface is a single-valued
`add_cugencodes("sm_89")`/`("native")`, so it has neither the quoting
defect nor a release matrix, and it does not build the published wheel.
Two follow-ups to the quoting fix, both about the architecture list reaching nvcc intact. Add a configure-time fast fail: read `CUDA_ARCHITECTURES` back off the `cuda` target and abort when it differs from `UIPC_CUDA_ARCHITECTURES`. The previous defect passed configure, build and packaging, and only surfaced when the binary met a GPU that was not in the (silently truncated) list, so a plain `FATAL_ERROR` here is worth more than any downstream test: a test binary is itself compiled with the same truncated list. Normalize `UIPC_CUDA_ARCHITECTURES` in place as well. Its documentation offers a comma form (`75,89`), but only `CMAKE_CUDA_ARCHITECTURES` was normalized, while the backend targets set their property straight from the option. The comma therefore survived configure and broke the first `.cu` compile with `nvcc fatal : '89' is not in 'keyword=value' format`. Normalizing the cache entry makes the documented form work and gives the new assertion a meaningful value to compare. Verified on the real project: configure succeeds and the assertion stays quiet for the full `75-real;80-real;86-real;89-real;120-real;89-virtual` list, for `native`, and for the comma form; all 199 CUDA translation units carry six `arch=compute_*,code=*` entries. The assertion was also shown to fire on the unquoted form, and a minimal end-to-end build confirms the comma form now compiles where it previously failed.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Release wheels declared five CUDA architectures but shipped one.
pyuipc0.0.27 (cp312 and cp313 manylinux both verified) contains
sm_75SASS only andzero PTX, although
pyproject.tomlandpython/src/uipc/compatibility.jsonboth asked for
75/80/86/89-realplus89-virtual.The cause is an unquoted variable:
An unquoted multi-element list expands into separate arguments, which breaks
the
PROPERTIESkey/value pairing. The property keeps only the first entry andthe rest are absorbed as bogus property names — measured directly: with
75-real;80-real;86-real;89-real;89-virtual,CUDA_ARCHITECTURESends up as75-realwhile80-realbecomes a property whose value is86-real. The89-virtualPTX entry disappears the same way. The argument count happens tostay even here, so CMake raises no error and the build and packaging both
succeed.
Consequence on consumer Blackwell (
sm_120):world.init(scene)throwsError 500 rather than 209
cudaErrorNoKernelImageForDevicebecause underCUDA 12.x lazy module loading a fatbin with no image for the current device
fails to load and its symbols are never registered. The developer default
nativeis a single element, so the defect cannot reproduce in a local build.This PR:
set_target_propertiessites(
src/backends/cuda/CMakeLists.txt,src/backends/cuda/components.cmake,apps/tests/backends/cuda/CMakeLists.txt).CUDA_ARCHITECTURESback off thecudatarget and aborts if it does not matchUIPC_CUDA_ARCHITECTURES, soa regression is a configure error instead of a silently narrowed wheel.
120-realto the release matrix for 50-series cards, mirrored incompatibility.jsonsoscripts/check_release_policy.pystays green.89-virtualremains the lowest virtual entry on purpose: PTX only JITsupward, so it is what still covers
sm_90andsm_100.UIPC_CUDA_ARCHITECTURESin place. The option's documentationoffers a comma form (
75,89), but onlyCMAKE_CUDA_ARCHITECTURESwasnormalized while the backend targets set their property straight from the
option, so the comma survived configure and broke the first
.cucompilewith
nvcc fatal : '89' is not in 'keyword=value' format. This also givesthe new assertion a meaningful value to compare.
XMake needs no counterpart. Its architecture surface is a single-valued
add_cugencodes("sm_89")undergithub_actionsandadd_cugencodes("native")otherwise, so it has neither the quoting defect nor a release matrix to mirror,
and the published wheel is built by scikit-build-core plus CMake.
Verification
compile_commands.jsonfor the CUDA backend: all 199 CUDA translation unitsnow carry the full list including
code=[compute_89]. Before the fix onlysm_75was emitted.whose
cuobjdump -all --list-elfreportssm_75 sm_80 sm_86 sm_89 sm_120and whose
cuobjdump -all -ptxreports.target sm_89, confirming CUDA 12.8accepts
120-real.passes on the quoted one.
six-entry list, for
native, and for the comma form. A minimal end-to-endbuild confirms
75,89now compiles where it previously hit thenvcc fatalabove.
scripts/check_release_policy.pypasses;scripts/format_changed.py --checkpasses (no C++ touched).
nativeon an RTX 5090 (sm_120) runs thehello_affine_bodyexample and the Python0_check_libuipcsample tocompletion.
Note for release planning: one more architecture increases wheel size and CI
compile time.
sm_90andsm_100still have no native SASS and rely on the89-virtualJIT path; extending the matrix further is a separate decision.Audit commands, the
cuobjdump --list-ptxcaveat (it reports nothing even forlibraries that do embed PTX — use
-ptx), and the quoting rule are recorded inagent_docs/08-pitfalls-and-debugging.md.Breaking Changes
None. The published architecture set only grows;
pyproject.tomlandcompatibility.jsonstay in sync, and no public API or behavior changes.Checklist
Fast fail
UIPC_ASSERTwhere feasible — n/a forCMake; the equivalent guard is the configure-time
FATAL_ERRORadded insrc/backends/cuda/CMakeLists.txtUIPC_ASSERT_THROW— n/a, no runtimeinput surface changed
C++ style
.clang-format— n/a, no C++ changed(
scripts/format_changed.py --checkclean)const std::string&/ no multipleinheritance — n/a
GPU / CUDA (if touched)
Constitution / material (if touched)
Build / bindings (if touched)
existing
UIPC_CUDA_ARCHITECTURESis only propagated correctly nowTests
uipc_add_test— n/a, the defect lives in the buildconfiguration and cannot be observed from a test binary that is itself
compiled with the truncated list
python/tests/— n/a, no binding surface changedregression guard, verified to fire on the unquoted form; no existing test
was modified