For the complete Mojo documentation index, see llms.txt. Markdown versions of all pages are available by appending .md to any URL (e.g. /docs/manual/basics.md).
DeviceGraphBuilder
struct DeviceGraphBuilder[arena_origin: ImmutOrigin]
Builder for explicit device graph construction.
A DeviceGraphBuilder is handed to the callback passed to
DeviceGraph.create().
Callers add kernel nodes via add_function() from within that callback,
which then instantiates a reusable DeviceGraph.
The builder, and any DeviceGraphNode handles it produces, are valid only
for the duration of the callback: their origin is scoped to the
DeviceGraph.create call and cannot escape it.
Example:
from std.gpu.host import DeviceContext, DeviceGraphBuilder
def kernel(x: Int):
print("Value:", x)
with DeviceContext() as ctx:
var compiled_fn = ctx.compile_function[kernel]()
def build(mut builder: DeviceGraphBuilder) raises {read}:
_ = builder.add_function(
compiled_fn, 42, grid_dim=1, block_dim=1, dependencies=[]
)
var graph = DeviceGraph.create(ctx, build)
graph.replay()
ctx.synchronize()
Parameters
- arena_origin (
ImmutOrigin): Origin of the enclosingDeviceGraph.createscope.
Implemented traits
AnyType,
ImplicitlyDeletable,
Movable
comptime members
Node
comptime Node = DeviceGraphNode[arena_origin]
Node handle type produced by this builder, branded with the builder's DeviceGraph.create scope origin.
Methods
__del__
def __del__(deinit self)
Releases resources associated with this graph builder.
context
def context(self) -> DeviceContext
Returns the device context this builder records against.
Unlike the context() accessors on buffer types, this is a non-raising
read of the builder's stored device context (the def declares no
raises).
Returns:
DeviceContext: The DeviceContext backing this builder.
add_function
def add_function[*Ts: DevicePassable](self, f: DeviceFunction[target=f.target, compile_options=f.compile_options, link_options=f.link_options, _ptxas_info_verbose=f._ptxas_info_verbose], *args: *Ts.values, *, grid_dim: Dim, block_dim: Dim, var dependencies: List[DeviceGraphNode[arena_origin]] = List(__list_literal__=NoneType(None)), cluster_dim: OptionalReg[Dim] = None, shared_mem_bytes: OptionalReg[Int] = None, var attributes: List[LaunchAttribute] = List(__list_literal__=NoneType(None)), var constant_memory: List[ConstantMemoryMapping] = List(__list_literal__=NoneType(None))) -> Self.Node
Adds a type-checked compiled kernel function as a node in this graph.
Parameters:
- *Ts (
DevicePassable): Argument types (must beDevicePassable).
Args:
- f (
DeviceFunction[target=f.target, compile_options=f.compile_options, link_options=f.link_options, _ptxas_info_verbose=f._ptxas_info_verbose]): The type-checked compiled function to add. Must have been compiled viaDeviceContext.compile_function(). - *args (
*Ts.values): Arguments to pass to the kernel. - grid_dim (
Dim): Dimensions of the compute grid. - block_dim (
Dim): Dimensions of each thread block. - dependencies (
List[DeviceGraphNode[arena_origin]]): Explicit list of predecessor node handles. An empty list makes the new node a graph root with no predecessors; a non-empty list uses those exact handles as predecessors. - cluster_dim (
OptionalReg[Dim]): Cluster dimensions (optional). - shared_mem_bytes (
OptionalReg[Int]): Amount of dynamic shared memory per block. - attributes (
List[LaunchAttribute]): Launch attributes. - constant_memory (
List[ConstantMemoryMapping]): Constant memory mappings.
Returns:
Self.Node: A handle to the newly added kernel-dispatch node.
Raises:
If adding the node fails.
def add_function[FuncType: def() -> None, //, dump_asm: Variant[Bool, Path, StringSlice[StaticConstantOrigin], def() capturing thin -> Path] = False, dump_llvm: Variant[Bool, Path, StringSlice[StaticConstantOrigin], def() capturing thin -> Path] = False, _dump_sass: Variant[Bool, Path, StringSlice[StaticConstantOrigin], def() capturing thin -> Path] = False, _ptxas_info_verbose: Bool = False](self, func: FuncType, grid_dim: Dim, block_dim: Dim, *, var dependencies: List[DeviceGraphNode[arena_origin]] = List(__list_literal__=NoneType(None)), cluster_dim: OptionalReg[Dim] = None, shared_mem_bytes: OptionalReg[Int] = None, var attributes: List[LaunchAttribute] = List(__list_literal__=NoneType(None)), var constant_memory: List[ConstantMemoryMapping] = List(__list_literal__=NoneType(None))) -> Self.Node
Compiles and adds a capturing kernel closure as a node in this graph.
This overload is for kernels that capture variables from their
enclosing scope using the {var} capture syntax. Compilation is
performed automatically using the DeviceContext that created this
builder, so no separate compile step is needed.
Example:
from std.gpu import global_idx
from std.gpu.host import DeviceContext, DeviceGraphBuilder
with DeviceContext() as ctx:
var scale: Float32 = 2.0
var buf = ctx.enqueue_create_buffer[DType.float32](256)
var ptr = buf.unsafe_ptr()
def scale_kernel() {var}:
var i = global_idx.x
ptr[i] = Float32(i) * scale
def build(mut builder: DeviceGraphBuilder) raises {read}:
_ = builder.add_function(
scale_kernel, grid_dim=1, block_dim=256, dependencies=[]
)
var graph = DeviceGraph.create(ctx, build)
graph.replay()
ctx.synchronize()
Parameters:
- FuncType (
def() -> None): The type of the closure function (usually inferred). - dump_asm (
Variant[Bool, Path, StringSlice[StaticConstantOrigin], def() capturing thin -> Path]): To dump the compiled assembly, passTrue, or a file path to dump to, or a function returning a file path. - dump_llvm (
Variant[Bool, Path, StringSlice[StaticConstantOrigin], def() capturing thin -> Path]): To dump the generated LLVM code, passTrue, or a file path to dump to, or a function returning a file path. - _dump_sass (
Variant[Bool, Path, StringSlice[StaticConstantOrigin], def() capturing thin -> Path]): Only runs on NVIDIA targets, and requires CUDA Toolkit to be installed. PassTrue, or a file path to dump to, or a function returning a file path. - _ptxas_info_verbose (
Bool): Only runs on NVIDIA targets, and requires CUDA Toolkit to be installed. Changesdump_asmto output verbose PTX assembly (defaultFalse).
Args:
- func (
FuncType): The capturing kernel closure to compile and add as a graph node. - grid_dim (
Dim): Dimensions of the compute grid. - block_dim (
Dim): Dimensions of each thread block. - dependencies (
List[DeviceGraphNode[arena_origin]]): Explicit list of predecessor node handles. An empty list makes the new node a graph root with no predecessors; a non-empty list uses those exact handles as predecessors. - cluster_dim (
OptionalReg[Dim]): Cluster dimensions (optional). - shared_mem_bytes (
OptionalReg[Int]): Amount of dynamic shared memory per block. - attributes (
List[LaunchAttribute]): Launch attributes. - constant_memory (
List[ConstantMemoryMapping]): Constant memory mappings.
Returns:
Self.Node: A handle to the newly added kernel-dispatch node.
Raises:
If adding the node fails.
def add_function[declared_arg_types: TypeList[declared_arg_types.values], //, func: def(*args: *declared_arg_types) thin -> None, *actual_arg_types: DevicePassable, *, link_options: StringSlice[StaticConstantOrigin] = StringSlice(""), dump_asm: Variant[Bool, Path, StringSlice[StaticConstantOrigin], def() capturing thin -> Path] = False, dump_llvm: Variant[Bool, Path, StringSlice[StaticConstantOrigin], def() capturing thin -> Path] = False, _dump_sass: Variant[Bool, Path, StringSlice[StaticConstantOrigin], def() capturing thin -> Path] = False, _ptxas_info_verbose: Bool = False](self, *args: *actual_arg_types.values, *, grid_dim: Dim, block_dim: Dim, var dependencies: List[DeviceGraphNode[arena_origin]] = List(__list_literal__=NoneType(None)), cluster_dim: OptionalReg[Dim] = None, shared_mem_bytes: OptionalReg[Int] = None, var attributes: List[LaunchAttribute] = List(__list_literal__=NoneType(None)), var constant_memory: List[ConstantMemoryMapping] = List(__list_literal__=NoneType(None)), func_attribute: OptionalReg[FuncAttribute] = None) -> Self.Node
Compiles and adds a kernel function as a node in this graph.
This overload takes the kernel as a compile-time parameter and
compiles it automatically using the DeviceContext that created this
builder, so no separate DeviceContext.compile_function() step is
needed. It mirrors the parameter-based
DeviceContext.enqueue_function()
overload for the non-graph path.
You can pass the function directly to add_function without compiling
it first:
from std.gpu.host import DeviceContext, DeviceGraphBuilder
def kernel(x: Int):
print("Value:", x)
with DeviceContext() as ctx:
def build(mut builder: DeviceGraphBuilder) raises {read}:
_ = builder.add_function[kernel](
42, grid_dim=1, block_dim=1, dependencies=[]
)
var graph = DeviceGraph.create(ctx, build)
graph.replay()
ctx.synchronize()
Parameters:
- declared_arg_types (
TypeList[declared_arg_types.values]): Types of the arguments to pass to the device function. - func (
def(*args: *declared_arg_types) thin -> None): The function to compile and add as a graph node. - *actual_arg_types (
DevicePassable): The types of the arguments being passed to the function. - link_options (
StringSlice[StaticConstantOrigin]): Additional linker flags and options as a string. - dump_asm (
Variant[Bool, Path, StringSlice[StaticConstantOrigin], def() capturing thin -> Path]): To dump the compiled assembly, passTrue, or a file path to dump to, or a function returning a file path. - dump_llvm (
Variant[Bool, Path, StringSlice[StaticConstantOrigin], def() capturing thin -> Path]): To dump the generated LLVM code, passTrue, or a file path to dump to, or a function returning a file path. - _dump_sass (
Variant[Bool, Path, StringSlice[StaticConstantOrigin], def() capturing thin -> Path]): Only runs on NVIDIA targets, and requires CUDA Toolkit to be installed. PassTrue, or a file path to dump to, or a function returning a file path. - _ptxas_info_verbose (
Bool): Only runs on NVIDIA targets, and requires CUDA Toolkit to be installed. Changesdump_asmto output verbose PTX assembly (defaultFalse).
Args:
- *args (
*actual_arg_types.values): Variadic arguments which are passed to thefunc. - grid_dim (
Dim): Dimensions of the compute grid. - block_dim (
Dim): Dimensions of each thread block. - dependencies (
List[DeviceGraphNode[arena_origin]]): Explicit list of predecessor node handles. An empty list makes the new node a graph root with no predecessors; a non-empty list uses those exact handles as predecessors. - cluster_dim (
OptionalReg[Dim]): Cluster dimensions (optional). - shared_mem_bytes (
OptionalReg[Int]): Amount of dynamic shared memory per block. - attributes (
List[LaunchAttribute]): Launch attributes. - constant_memory (
List[ConstantMemoryMapping]): Constant memory mappings. - func_attribute (
OptionalReg[FuncAttribute]):CUfunction_attributeenum.
Returns:
Self.Node: A handle to the newly added kernel-dispatch node.
Raises:
If adding the node fails.
add_copy
def add_copy[dtype: DType](self, dst_buf: DeviceBuffer[dtype], src_buf: HostBuffer[dtype], *, var dependencies: List[DeviceGraphNode[arena_origin]] = List(__list_literal__=NoneType(None))) -> Self.Node
Adds a host-to-device memcpy node to the graph.
The number of bytes copied is determined by the size of the device buffer.
Parameters:
- dtype (
DType): Type of the data being copied.
Args:
- dst_buf (
DeviceBuffer[dtype]): Device buffer to copy to. - src_buf (
HostBuffer[dtype]): Host buffer to copy from. - dependencies (
List[DeviceGraphNode[arena_origin]]): Explicit list of predecessor node handles. An empty list makes the new node a graph root with no predecessors; a non-empty list uses those exact handles as predecessors.
Returns:
Self.Node: A handle to the newly added memcpy node.
Raises:
If adding the node fails.
def add_copy[dtype: DType](self, dst_buf: HostBuffer[dtype], src_buf: DeviceBuffer[dtype], *, var dependencies: List[DeviceGraphNode[arena_origin]] = List(__list_literal__=NoneType(None))) -> Self.Node
Adds a device-to-host memcpy node to the graph.
The number of bytes copied is determined by the size of the device buffer.
Parameters:
- dtype (
DType): Type of the data being copied.
Args:
- dst_buf (
HostBuffer[dtype]): Host buffer to copy to. - src_buf (
DeviceBuffer[dtype]): Device buffer to copy from. - dependencies (
List[DeviceGraphNode[arena_origin]]): Explicit list of predecessor node handles. An empty list makes the new node a graph root with no predecessors; a non-empty list uses those exact handles as predecessors.
Returns:
Self.Node: A handle to the newly added memcpy node.
Raises:
If adding the node fails.
def add_copy[dtype: DType](self, dst_buf: DeviceBuffer[dtype], src_buf: DeviceBuffer[dtype], *, var dependencies: List[DeviceGraphNode[arena_origin]] = List(__list_literal__=NoneType(None))) -> Self.Node
Adds a device-to-device memcpy node to the graph.
Both buffers must belong to the same context as this builder; cross-context copies are not supported in graphs. The number of bytes copied is determined by the size of the source buffer.
Parameters:
- dtype (
DType): Type of the data being copied.
Args:
- dst_buf (
DeviceBuffer[dtype]): Device buffer to copy to. - src_buf (
DeviceBuffer[dtype]): Device buffer to copy from. Must be the same size asdst_buf. - dependencies (
List[DeviceGraphNode[arena_origin]]): Explicit list of predecessor node handles. An empty list makes the new node a graph root with no predecessors; a non-empty list uses those exact handles as predecessors.
Returns:
Self.Node: A handle to the newly added memcpy node.
Raises:
If adding the node fails.
add_memset
def add_memset[dtype: DType](self, dst: DeviceBuffer[dtype], val: Scalar[dtype], *, var dependencies: List[DeviceGraphNode[arena_origin]] = List(__list_literal__=NoneType(None))) -> Self.Node
Adds a memset node to the graph that sets all elements of dst to val.
Parameters:
- dtype (
DType): Type of the data stored in the buffer.
Args:
- dst (
DeviceBuffer[dtype]): Destination buffer. - val (
Scalar[dtype]): Value to set all elements ofdstto. - dependencies (
List[DeviceGraphNode[arena_origin]]): Explicit list of predecessor node handles. An empty list makes the new node a graph root with no predecessors; a non-empty list uses those exact handles as predecessors.
Returns:
Self.Node: A handle to the newly added memset node.
Raises:
If adding the node fails. The underlying graph APIs cannot express an 8-byte memset whose high and low 32-bit halves differ as a single node, so such patterns will return an error.
add_empty
def add_empty(self, *, var dependencies: List[DeviceGraphNode[arena_origin]] = List(__list_literal__=NoneType(None))) -> Self.Node
Adds an empty (no-op) node to the graph.
Empty nodes perform no work at execution time. They are used purely
for transitive ordering: a single empty node fanned in from m
predecessors and out to n successors expresses an m-to-n
barrier using m + n edges instead of m * n, and serves as a
stable handle for "the completion of this phase" when the producer
set is not visible to the consumer.
Args:
- dependencies (
List[DeviceGraphNode[arena_origin]]): Explicit list of predecessor node handles. An empty list makes the new node a graph root with no predecessors; a non-empty list uses those exact handles as predecessors.
Returns:
Self.Node: A handle to the newly added empty node.
Raises:
If adding the node fails.
region
def region(mut self, work: T, *, var dependencies: List[DeviceGraphNode[arena_origin]] = List(__list_literal__=NoneType(None))) -> Self.Node
Runs work and returns a single empty node that joins every node added to this builder during its execution.
The returned handle is suitable for use as a one-element
dependencies= entry on a downstream add_* call. The empty
node performs no work at execution time; it exists purely as a
fan-in barrier so the caller does not need to thread the
producer set's individual handles to every consumer.
Every node work adds also depends on the predecessors named in
dependencies: while work runs, those handles are injected as
ambient predecessors that each add_* call unions into its own
dependencies. This makes the region's nodes run after the named
predecessors without the closure having to thread the handles
through to every add_* call. With the default (empty)
dependencies, the region's nodes are unconstrained relative to
earlier work.
Example:
from std.gpu.host import DeviceContext, DeviceGraphBuilder
with DeviceContext() as ctx:
var buf_a = ctx.enqueue_create_buffer[DType.uint8](100)
var buf_b = ctx.enqueue_create_buffer[DType.uint8](100)
var buf_c = ctx.enqueue_create_buffer[DType.uint8](100)
var host_src = ctx.enqueue_create_host_buffer[DType.uint8](100)
def build(mut builder: DeviceGraphBuilder) raises {read}:
def add_producers(mut b: DeviceGraphBuilder) raises {read} -> None:
_ = b.add_memset(buf_a, UInt8(1), dependencies=[])
_ = b.add_memset(buf_b, UInt8(2), dependencies=[])
var producers_join = builder.region(add_producers)
_ = builder.add_copy(
buf_c, host_src, dependencies=[producers_join]
)
var graph = DeviceGraph.create(ctx, build)
graph.replay()
Args:
- work (
T): Closure whose effects on this builder are captured. The builder is passed aswork's sole argument; the closure must not capture the same builder, since doing so would alias with this method's receiver. The closure may add any number of nodes (zero or more) via any of theadd_*methods. - dependencies (
List[DeviceGraphNode[arena_origin]]): Predecessor node handles that every node added byworkshould depend on. Defaults to empty (no added predecessors).
Returns:
Self.Node: A handle that successors can depend on to run after everything
work added. When work adds two or more nodes, this is a fresh
empty node that joins them; when it adds exactly one node, that
node is returned directly (no extra empty node); when it adds none,
the returned empty node falls back to depending on dependencies
so it still chains correctly.
Raises:
Anything work itself raises, or anything raised while
adding the join node.
add_output
def add_output(self, var output: AnyAsyncValueRef)
Add a value as an output for the resulting device graph.
The graph records the output so its backing memory outlives the graph that references it. Ownership of the async value is transferred to the builder.
Args:
- output (
AnyAsyncValueRef): The async value to register as a graph output.
num_outputs
def num_outputs(self) -> Int
Returns the number of outputs registered on the device graph.
Returns:
Int: The number of outputs added via add_output.