cuda::experimental::stf::stackable_ctx#
-
class stackable_ctx#
This class defines a context that behaves as a context which can have nested subcontexts (implemented as local CUDA graphs)
Public Types
-
template<typename T>
using logical_data_t = ::cuda::experimental::stf::stackable_logical_data<T>#
Public Functions
-
inline stackable_ctx()#
-
inline auto &get_node(size_t offset)#
-
inline int get_parent_offset(int offset) const#
-
inline const auto &get_children_offsets(int parent) const#
-
inline int get_root_offset() const#
-
inline int get_head_offset() const#
-
inline bool has_head_set() const#
True if the current thread has a head offset set (has entered the context).
-
inline void set_head_offset(int offset)#
- inline void push(
- const ::cuda::std::source_location loc = ::cuda::std::source_location::current()
-
template<typename Policy = exception_policies::rethrow_t>
inline void pop( - Policy policy = {}
Terminate the current nested level and get back to the previous one.
Error-handling contract, shared by pop(), pop_epilogue() and the RAII scopes: a function that begins a level (push(), pop_prologue()) may throw and leaves nothing behind; a function that ends one completes the transition whatever its steps report (data is unfrozen, the node is destroyed, the head moves back to the parent) and then rethrows the first failure, so the caller receives the error together with a consistent context. Asynchronous device errors surface here, at the synchronize inside the nested finalize, which is why this is the function most worth catching around.
policydecides what happens to that first failure once the level is gone: the default,rethrow, propagates it with its type intact;abortreports and ends the program, which is what the RAII scopes pass since they call pop() from a destructor;notifyreports and lets the program continue. See exception_policy.cuh for the full set.
-
inline launchable_graph_handle pop_prologue()#
First phase of a re-launchable pop.
Runs the same prologue as pop() and finalizes the nested cudaGraph_t, but does NOT instantiate the cudaGraphExec_t, does NOT launch the graph, and does NOT release resources. Instantiation (cache lookup + cudaGraphInstantiate) is deferred to the first launchable_graph_handle exec()/launch() call, so callers that only consume handle.graph() never pay the instantiation cost. Returns a launchable_graph_handle the caller can use to launch the graph one or more times. pop_epilogue() must be called exactly once afterwards to release resources and destroy the node.
Only legal when the head context is a top-level graph (parent is the stream_ctx root). Aborts otherwise.
Defined out-of-line in stackable_ctx.cuh since the return type (launchable_graph_handle) is only declared there.
-
template<typename Policy = exception_policies::rethrow_t>
inline void pop_epilogue( - Policy policy = {}
Second phase of a re-launchable pop.
Releases resources, unfreezes any data that was pushed into the nested context, and destroys the node. Invalidates every launchable_graph_handle that was produced by the matching pop_prologue(). Completes even if a step fails, then hands the first failure to
policy; see the contract on pop().
Shared-ownership flavor of pop_prologue().
Runs pop_prologue() and wraps the resulting launchable_graph_handle into a copyable / storable
launchable_graphwhose destructor runs pop_epilogue() when the last shared copy dies. Use this when you want to build a graph, stash it as a data member / in a container / return it across function boundaries, and launch it many times before releasing.Defined out-of-line in stackable_ctx.cuh since the return type is only declared there.
- inline graph_scope_guard graph_scope(
- ::cuda::std::source_location loc = ::cuda::std::source_location::current()
-
template<typename T, typename ...Sizes>
inline auto logical_data( - size_t elements,
- Sizes... more_sizes
-
template<typename T, typename ...Sizes>
inline auto logical_data_no_export( - size_t elements,
- Sizes... more_sizes
-
inline stackable_logical_data<void_interface> token()#
-
template<typename ExecPlace, typename ...Deps, ::cuda::std::enable_if_t<::cuda::std::is_base_of_v<exec_place, ::cuda::std::decay_t<ExecPlace>>, int> = 0>
inline auto task(
)#
-
inline auto fence()#
-
template<typename T>
inline auto wait( - ::cuda::experimental::stf::stackable_logical_data<T> &ldata
-
inline auto get_dot()#
-
inline void pop_affinity() const#
-
inline auto ¤t_affinity() const#
-
inline const exec_place ¤t_exec_place() const#
-
inline auto &async_resources() const#
-
inline auto dot_section(::std::string symbol) const#
-
inline size_t task_count() const#
-
inline void finalize()#
- inline ::std::unique_lock<::std::shared_mutex> acquire_exclusive_lock(
-
template<typename ...Deps>
class deferred_task_builder# Defers task creation until all dependencies are known.
In a regular context, task() immediately creates the underlying task and add_deps() appends to it — access modes are already resolved.
In a stackable context, we cannot create the task immediately because add_deps() may later introduce additional dependencies on the same logical data with a different access mode (e.g. read initially, then write via add_deps). The data must be imported (pushed/frozen) with the combined mode (rw in that example), but once data is frozen with a given mode it cannot be upgraded.
deferred_task_builder therefore collects all dependencies first, combines access modes per logical data, validates and auto-pushes with the correct combined mode, and only then creates the real task via the underlying context. The task is concretized lazily on the first call to operator->* (when the task body is provided) or set_symbol.
Public Functions
-
template<typename ExecPlace>
inline deferred_task_builder( - stackable_ctx &sctx,
- int offset,
- ExecPlace &&exec_place,
- Deps&&... deps
-
inline auto &start()#
-
inline auto &set_symbol(::std::string s) &#
-
inline auto &&set_symbol(::std::string s) &&#
-
template<typename T>
inline auto get(size_t index) const#
Public Members
-
stackable_ctx &sctx_#
-
int offset_#
-
exec_place exec_place_#
-
::std::vector<additional_dep_info> additional_deps_#
-
::cuda::std::optional<::std::string> symbol_#
-
struct additional_dep_info#
Public Members
-
int logical_data_id#
-
access_mode mode#
-
::std::function<void(stackable_ctx&, int, access_mode)> validate_access_op#
-
::std::function<task_dep_untyped(int, access_mode)> resolve_op#
-
int logical_data_id#
-
template<typename ExecPlace>
-
class graph_scope_guard#
RAII wrapper for automatic push/pop management (lock_guard style)
This class provides automatic scope management for nested contexts, following the same semantics as std::lock_guard. The constructor calls push() and the destructor calls pop().
Usage (direct constructor style):
{ stackable_ctx::graph_scope_guard scope{ctx}; // nested context operations... }
Usage (factory method style):
{ auto scope = ctx.graph_scope(); // nested context operations... }
Public Types
-
using context_type = stackable_ctx#
Public Functions
- inline explicit graph_scope_guard(
- stackable_ctx &ctx,
- ::cuda::std::source_location loc = ::cuda::std::source_location::current()
-
inline ~graph_scope_guard()#
-
graph_scope_guard(const graph_scope_guard&) = delete#
-
graph_scope_guard &operator=(const graph_scope_guard&) = delete#
-
graph_scope_guard(graph_scope_guard&&) = delete#
-
graph_scope_guard &operator=(graph_scope_guard&&) = delete#
-
using context_type = stackable_ctx#
-
class impl#
Public Functions
-
inline impl()#
-
inline ~impl()#
-
template<typename ContextType>
inline async_resources_handle get_async_handle( - const ContextType &parent_ctx
Helper to get async handle from pool or create new one.
- inline void push(
- const ::cuda::std::source_location &loc,
- bool is_root = false,
- const push_while_config &config = push_while_config{}
Create a new nested level.
head_offset is the offset of thread’s current top context (-1 if none)
-
inline void _pop_prologue()#
- inline void _pop_epilogue(
- event_list &finalize_prereqs,
- ::std::exception_ptr &err
-
template<typename Policy>
inline void pop(Policy policy)# Terminate the current nested level and get back to the previous one.
-
inline pop_prologue_result pop_prologue_impl()#
First phase of a two-phase pop: runs _pop_prologue() and prepare_graph() so the caller can obtain the finalized graph and launch it one or more times before calling pop_epilogue_impl().
The cudaGraphExec_t is instantiated lazily on the first exec()/launch(), not here.
Only legal on a non-nested graph_ctx_node (top-level graph whose parent is the stream_ctx root).
-
template<typename Policy>
inline void pop_epilogue_impl( - Policy policy
Second phase of a two-phase pop: finalises resources and runs _pop_epilogue() to actually destroy the node.
Invalidates every launchable_graph_handle that was produced by the matching pop_prologue().
- inline void launch_prepared_graph(
- int node_offset,
- cudaStream_t stream
Dispatch one launch of the executable graph prepared by pop_prologue_impl().
Called from launchable_graph_handle::launch().
Triggers cache lookup + instantiation on first call (idempotent afterwards). The prereq sync into
streamis performed bylaunch_onceon its first invocation.
- inline ::std::shared_ptr<cudaGraphExec_t> prepare_handle_for_exec(
- int node_offset
Lazily instantiate the graph (cache query + cudaGraphInstantiate if not cached) and sync the support_stream behind the parent’s freeze events, then return the shared executable graph.
Called from
launchable_graph_handle::exec().Idempotent: subsequent calls skip both steps. The returned shared_ptr stays valid until
pop_epilogue().
-
inline void prepare_handle_for_graph(int node_offset)#
Lazily sync the support stream behind the parent’s freeze events without instantiating the exec graph.
Called from
launchable_graph_handle::graph()so that a caller embedding the nested graph as a child node can treathandle.stream()as a ready event source for dep-A ordering.Idempotent. Does NOT trigger
cudaGraphInstantiate.
-
inline int get_root_offset() const#
-
inline ::std::unique_ptr<ctx_node_base> &get_node(int offset)#
- inline const ::std::unique_ptr<ctx_node_base> &get_node(
- int offset
-
inline int get_head_offset() const#
-
inline bool has_head_set() const#
True if the current thread has a head offset set (has entered the context).
-
inline void set_head_offset(int offset)#
-
inline int get_parent_offset(int offset) const#
-
inline const auto &get_children_offsets(int parent) const#
- inline ::std::unique_lock<::std::shared_mutex> acquire_exclusive_lock(
-
struct pop_prologue_result#
-
inline impl()#
-
class launchable_graph#
Shared-ownership, storable handle for a re-launchable popped graph.
Returned by
stackable_ctx::pop_prologue_shared(). Copies share a single underlying state; when the last copy is destroyed (or the last copy is explicitlyreset()),pop_epilogue()runs on the originating context.Unlike
launchable_graph_scope, this type is copyable and movable, so it can be stored as a data member, placed in containers, or returned from a factory —making it a natural fit for a classic “build once, launch many
times, release later” cache.
Example — build once, store as a data member, launch repeatedly:
class SimEngine { public: void build(size_t N, double alpha) { ctx_.push(); auto lx = ctx_.logical_data(shape_of<slice<double>>(N)); ctx_.parallel_for(lx.shape(), lx.write())->*[] __device__(size_t i, auto x) { x(i) = 1.0; }; ctx_.parallel_for(lx.shape(), lx.rw())->*[=] __device__(size_t i, auto x) { x(i) += alpha; }; step_graph_ = ctx_.pop_prologue_shared(); } void step() { step_graph_.launch(); } private: stackable_ctx ctx_; stackable_ctx::launchable_graph step_graph_; };
Example — cache keyed by shape:
std::unordered_map<size_t, stackable_ctx::launchable_graph> cache; if (auto it = cache.find(N); it == cache.end()) { ctx.push(); // ... build graph ... cache.emplace(N, ctx.pop_prologue_shared()); } cache[N].launch();
Example — embed into a larger graph instead of launching:
auto sub = ctx.pop_prologue_shared(); cudaGraph_t outer = nullptr; cudaGraphCreate(&outer, 0); cudaGraphNode_t child{}; // graph() does NOT instantiate; sub.stream() is a valid event source. cudaGraphAddChildGraphNode(&child, outer, nullptr, 0, sub.graph());
Public Functions
-
launchable_graph() = default#
-
launchable_graph(const launchable_graph&) = default#
-
launchable_graph(launchable_graph&&) noexcept = default#
-
launchable_graph &operator=(const launchable_graph&) = default#
-
launchable_graph &operator=(launchable_graph&&) noexcept = default#
-
~launchable_graph() = default#
-
inline void launch()#
Launch the graph once on its support stream.
-
inline cudaGraphExec_t exec() const#
Underlying executable graph.
Triggers lazy instantiation + dep-A sync on the first call (same contract as
launchable_graph_handle::exec()).
-
inline cudaStream_t stream() const#
Support stream the graph was prepared against. Purely observational.
-
inline cudaGraph_t graph() const#
Underlying cudaGraph_t topology (for embedding as a child graph).
Triggers lazy dep-A sync but does NOT call
cudaGraphInstantiate.
-
inline bool valid() const noexcept#
True iff this copy still holds a shared reference and the underlying pop has not been epilogued (e.g.
manually via
ctx.pop_epilogue()).
-
inline explicit operator bool() const noexcept#
-
inline long use_count() const noexcept#
Number of live shared copies referring to the same graph.
Debug introspection only. Returns 0 for a default-constructed / moved-from instance.
-
inline void reset() noexcept#
Drop this shared reference eagerly.
When this was the last copy,
pop_epilogue()runs now instead of at destruction time. Idempotent.
-
launchable_graph() = default#
-
class launchable_graph_scope#
RAII wrapper for a re-launchable pop scope.
On construction, calls
ctx.push(). The caller builds the nested graph body, then useslaunch()(orexec()/stream()/graph()) as many times as desired; the first such call triggersctx.pop_prologue(). The destructor (or an explicitrelease()) runsctx.pop_epilogue()and invalidates the handle.Usage:
stackable_ctx ctx; { stackable_ctx::launchable_graph_scope scope{ctx}; // ... build graph contents as if inside ctx.push()/ctx.pop() ... for (int i = 0; i < N; ++i) scope.launch(); } // pop_epilogue() runs automatically here
Public Types
-
using context_type = stackable_ctx#
Public Functions
- inline explicit launchable_graph_scope(
- stackable_ctx &ctx,
- ::cuda::std::source_location loc = ::cuda::std::source_location::current()
-
inline ~launchable_graph_scope() noexcept#
-
launchable_graph_scope(const launchable_graph_scope&) = delete#
- launchable_graph_scope &operator=(
- const launchable_graph_scope&
-
launchable_graph_scope(launchable_graph_scope&&) = delete#
-
launchable_graph_scope &operator=(launchable_graph_scope&&) = delete#
-
inline void launch()#
Launch the graph once. The first call triggers pop_prologue().
-
inline cudaGraphExec_t exec()#
Expose the executable graph. Triggers pop_prologue() on demand.
-
inline cudaStream_t stream()#
Expose the support stream. Triggers pop_prologue() on demand.
-
inline cudaGraph_t graph()#
Expose the underlying CUDA graph (for embedding into another graph via cudaGraphAddChildGraphNode).
Triggers pop_prologue() on demand.
-
inline void release() noexcept#
Explicitly commit the pop (idempotent).
Runs pop_prologue() (if not already done) and pop_epilogue(). After release(), further calls to launch()/exec()/stream()/graph() are invalid.
Tearing the graph node down is not something a caller could retry or recover from, so a failure here terminates.
-
using context_type = stackable_ctx#
-
template<typename T>