cuda.core: DeviceMemoryResource setup is refused under non-relaxed stream capture, and tearing down the invalidated GraphBuilder segfaults
維護者通常 1 天內回覆
還沒有人認領這個 Issue。
評估
研究方向
從 cuda_core/cuda/core/_memory/_device_memory_resource.pyx 和 _memory_pool.pyx 開始,重點關注 _DMR_init 和 MP_raise_release_threshold。接著檢查 _graph_builder.pyx 和 cuda/core/_cpp/resource_handles.cpp 中 GraphBuilder 的 begin/end 路徑以及 create_graph_handle。完成的標準是實現 capture-safe 的資源設定,使失效的 captures 結束時不會銷毀過時的 graph,並補充回歸覆蓋,其中包括 subprocess no-crash 檢查。
由索引模型根據 Issue 內容生成。
描述
Summary
Two related defects surfaced while checking whether cuda.core is affected by the libcu++ issue fixed in NVIDIA/cccl#11360 (default memory pool resolution fails under stream capture).
-
DeviceMemoryResource(device)with no options wraps the driver's current pool for the device and raises its release threshold. That step callscuMemPoolGetAttribute, andcuMemPoolSetAttributewhen the threshold is zero. The driver treats both as potentially unsafe calls while the calling thread is inside a global or thread-local capture: the constructor raises and the capture is invalidated.Device.memory_resourceconstructs this resource lazily on first access, so a first allocation throughDevice.allocatecan invalidate a capture that is in progress. cuda.core's ownGraphBuilder.begin_building()defaults to"relaxed", which passes the check, so this hits code that asks for"global"or"thread_local", and captures started by other libraries on the same thread (PyTorch's graph capture defaults to global mode). A global capture on another thread is invalidated too. -
After any invalidated capture, tearing down the
GraphBuildersegfaults insidecuGraphDestroy. The builder holds an owning handle to the capture graph it obtained fromcuStreamGetCaptureInfo. When the capture was invalidated,cuStreamEndCapturereturns a NULL graph and the driver releases the capture graph itself, so the builder's latercuGraphDestroyis a use-after-free. This is independent of item 1: any invalidation triggers it, in all three capture modes, and the minimal reproducer crashes deterministically.
What was observed
cuda.core main at 4357f375, cuda.bindings 13.4.1, Python 3.14.7, Linux x86_64. Reproduced on an H100 PCIe (driver 610.57.04) and an H200 (driver 595.58.03). Each scenario below runs in a fresh process.
| Scenario, with a capture active in the given mode | global |
thread_local |
relaxed |
|---|---|---|---|
DeviceMemoryResource(device) |
raises CUDA_ERROR_STREAM_CAPTURE_UNSUPPORTED; capture invalidated |
same | ok; capture stays active |
first access of Device.memory_resource |
raises CUDA_ERROR_STREAM_CAPTURE_UNSUPPORTED; capture invalidated |
same | ok; capture stays active |
| another thread holds the capture; this thread constructs the resource | raises; the other thread's capture is invalidated (its teardown then hits item 2) | ok | ok |
Item 1, minimal reproducer:
from cuda.core import Device, DeviceMemoryResource
dev = Device()
dev.set_current()
gb = dev.create_stream().create_graph_builder().begin_building(mode="global") # or "thread_local"
DeviceMemoryResource(dev) # raises CUDAError: CUDA_ERROR_STREAM_CAPTURE_UNSUPPORTED
gb.is_building # RuntimeError: the build process has been invalidated
Item 2, minimal reproducer. Device.sync() is refused whenever a stream in the context is capturing, which is expected and is used here only to invalidate the capture. The crash afterwards is the bug. It reproduces in all three modes:
from cuda.core import Device
dev = Device()
dev.set_current()
gb = dev.create_stream().create_graph_builder().begin_building()
try:
dev.sync() # refused under capture; the capture is now invalidated
except Exception:
pass
try:
gb.end_building() # RuntimeError: invalidated; the capture is not ended
except Exception:
pass
del gb # segfault inside cuGraphDestroy
Backtrace of the crash (trimmed):
#3 cuGraphDestroy () from libcuda.so.1
#5 operator() at cuda/core/_cpp/resource_handles.cpp:1958 # GraphHierarchy deleter from create_graph_handle
#13 __Pyx_call_destructor<std::shared_ptr<CUgraph_st* const>> at _graph_builder.cpp
#14 __pyx_tp_dealloc ... GraphBuilder
#15 _Py_Dealloc
Analysis
Item 1 lives in _DMR_init (cuda_core/cuda/core/_memory/_device_memory_resource.pyx), which calls MP_raise_release_threshold (_memory_pool.pyx) on the no-options path. The pool lookup itself (cuDeviceGetMemPool) is a query and is legal under capture; only the attribute read and write are refused. The pinned and managed resources do not raise the threshold and are unaffected. Other pool calls (owned-pool create and destroy, attributes reads, peer_accessible_by, IPC export and import) are refused under the same rule, but they are explicit user actions and are out of scope here.
Item 2: begin_building calls cuStreamGetCaptureInfo after cuStreamBeginCapture and wraps the capturing graph with create_graph_handle, whose deleter calls cuGraphDestroy. GB_end_capture_if_needed (called from close() and __dealloc__) then calls cuStreamEndCapture; for an invalidated capture the driver returns the invalidation error and a NULL graph, and the builder's owning handle still destroys the stale graph afterwards. Two related gaps make the situation unrecoverable from Python: end_building() raises from is_building before it ever calls cuStreamEndCapture, and close() raises from the end-capture error before it resets its handles. #2776 is a sibling: a fork left mid-capture also crashes at collection.
Suggested direction
Item 1: run the threshold read and write inside a relaxed-capture scope, mirroring the CCCL fix. cuThreadExchangeStreamCaptureMode swaps the calling thread's mode to relaxed and the scope restores the previous mode on exit. It is per-thread, needs no stream, costs two cheap driver calls, and has no observable effect when the thread is not capturing. The attribute write executes immediately rather than being recorded, which is the intent for a process-wide setting. Add a test that constructs DeviceMemoryResource and touches Device.memory_resource under each capture mode and checks that the capture stays valid.
Item 2: the builder must not own the capture graph while the capture is in progress. Hold a non-owning handle from begin_building, and take ownership only of the graph returned by a successful cuStreamEndCapture; equivalently, release _h_graph without cuGraphDestroy whenever end-capture returns NULL or an error. Make end_building() and close() end an invalidated capture and raise the invalidation error, leaving the builder closed and safe to collect. Add a regression test, run in a subprocess, that invalidates a capture, drops the builder, and expects no crash.
- 主要語言
- Cython
- 星號
- 3.4k
- 分支
- 335
- 平均合併
- 2 天 4 小時
- 30 天內合併 PR
- 131
環境準備
- 沒有 Dockerfile 或 Docker Compose 檔案
- 有 Pull Request 範本
- 閱讀貢獻指南
從這裡開始
- 先讀完整個 Issue,再讀專案的貢獻指南。
- 在 Issue 下留言說明你要接手 —— 這能避免兩個人做同樣的事。
- Fork 儲存庫,在一個分支上完成修改。
- 送出 Pull Request,並在描述裡引用這個 Issue 編號。
NVIDIA/cuda-python 的其他 Issue
-
[BUG]: cleanup-pr-previews never removes previews of deleted PRs可能已有人在做 @rwgk 於 8 天前認領。 未關閉
難度 2/5 1-3 小時 新手友好度 84/100
NVIDIA/cuda-python#2952 · 已指派 1 人 ·
維護者通常 1 天內回覆
-
[DOC]: cuda.core 1.1.1 note misstates program cache permissions可能已有人在做 @leofang 於 23 天前認領。 未關閉documentation P1
難度 1/5 1 小時以內 新手友好度 88/100
NVIDIA/cuda-python#2717 · 已指派 1 人 ·
維護者通常 1 天內回覆
-
[DOC]: `PinnedMemoryResource.allocate` documents no parameters可能已有人在做 @Andy-Jost 於 23 天前認領。 未關閉cuda.core documentation P1
難度 1/5 1-3 小時 新手友好度 90/100
NVIDIA/cuda-python#2712 · 已指派 1 人 ·
維護者通常 1 天內回覆
-
[BUG]: LocatedHeaderDir is mutable, so callers can poison the cached header-directory lookup可能已有人在做 @rwgk 於 23 天前認領。 未關閉triage
難度 2/5 1-3 小時 新手友好度 82/100
NVIDIA/cuda-python#2646 · 1 個 reaction ·
維護者通常 1 天內回覆
-
[FEA]: Support inheritance from Buffer可能已有人在做 @leofang 於 23 天前認領。 未關閉cuda.core triage
難度 2/5 1-3 小時 新手友好度 62/100
NVIDIA/cuda-python#2435 · 1 則留言 ·
維護者通常 1 天內回覆
查看 NVIDIA/cuda-python 的全部 Issue
相似的 Issue
-
難度 2/5 1-3 小時 新手友好度 72/100
-
難度 2/5 1-3 小時 新手友好度 72/100
ROCm/rocm-systems#13525 ·
維護者通常 2 天內回覆
-
難度 2/5 1 小時以內 新手友好度 80/100
-
[Bug]: copy-assigning a boundary condition does not compile: `Bc::operator=` assigns to the `const` reference `m_domain`可能已有人在做 @gouarin 於 7 天前認領。 未關閉bug good first issue
難度 2/5 1-3 小時 新手友好度 78/100
維護者通常 1 天內回覆
-
amr bug 🔥 diagnostics outputs
難度 2/5 1-3 小時 新手友好度 78/100
維護者通常 2 天內回覆