You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
cuda.core graph APIs use a fixed-size buffer for a single driver call, then assume the out-count is the true total when deciding whether to re-query. CUDA driver graph query APIs clamp the out-count to the buffer capacity when the real size is larger, so the grow-and-retry path is unreachable and results are silently truncated.
Tracked as internal bug 6572657. Same root cause in four places (introduced with #1859):
API
File
Threshold
node.pred / node.succ iteration (query)
_adjacency_set_proxy.pyx
16
x in node.pred/succ (contains)
_adjacency_set_proxy.pyx
16
GraphDefinition.nodes()
_graph_definition.pyx
128
GraphDefinition.edges()
_graph_definition.pyx
128
clear() and pred/succ setters iterate then delete one-by-one. Iteration stops at 16, so each call only removes 16 edges; remaining edges stay in the graph with no error. Users can build a CUDA graph whose execution order does not match the topology they intended.
Observed with 20 successors:
len(hub.succ) = 20 # correct (NULL count path)
len(list(hub.succ)) = 16 # truncated
kids[19] in hub.succ = False
hub.succ.clear() → len(g.edges()) = 4 # should be 0
Also: len(list(GraphDefinition with 130 empty nodes).nodes()) == 128 (not 130).
Driver APIs such as cuGraphNodeGetDependentNodes / cuGraphGetNodes / cuGraphGetEdges fill the buffer and return success with out-count equal to capacity (copied count) when capacity is too small — not the true total. See cuGraphNodeGetDependencies. cuda.core treated that out-count as the true total.
Adjacency iteration, membership, clear(), GraphDefinition.nodes(), and GraphDefinition.edges() should report the full topology. When the initial buffer is too small, cuda.core should re-query with a correctly sized buffer (or an equivalent correct pattern) rather than treating the clamped out-count as complete.
A small-buffer-optimized vector would eliminate this class of bugs. It lets you keep the correct two-call pattern while still avoiding heap allocation in the common small case:
No “grow and retry” logic needed — the container handles stack vs heap internally based on the true count. This removes the temptation to optimize away the NULL first-call, which is what caused this bug.
Established implementations: boost::container::small_vector, llvm::SmallVector, folly::small_vector, eastl::fixed_vector<T, N, /overflow=/true>.
Regular C static arrays are natively supported in Cython. What is the advantage of these other things? I suppose it's bounds checking but I'm not sure Cython would end up exercising whatever bounds checking they provide.
Activity
rparolin commentedon Aug 7, 2026
A small-buffer-optimized vector would eliminate this class of bugs. It lets you keep the correct two-call pattern while still avoiding heap allocation in the common small case:
No “grow and retry” logic needed — the container handles stack vs heap internally based on the true count. This removes the temptation to optimize away the NULL first-call, which is what caused this bug.
Established implementations: boost::container::small_vector, llvm::SmallVector, folly::small_vector, eastl::fixed_vector<T, N, /overflow=/true>.
mdboom commentedon Aug 7, 2026
Regular C static arrays are natively supported in Cython. What is the advantage of these other things? I suppose it's bounds checking but I'm not sure Cython would end up exercising whatever bounds checking they provide.