C++ and CUDA Workflows#
Warp is primarily authored from Python, but several workflows expose generated C++/CUDA code or replay captured Warp work from a native host application. This page collects the public entry points for those non-Python integration paths and links to the detailed workflow documentation and examples.
Use this page when you need to:
insert native C++/CUDA snippets into generated Warp kernels.
ahead-of-time compile Warp kernels into source, PTX, or CUBIN files.
load generated Warp binaries or source from a CUDA C++ application.
serialize captured Warp work and replay it from C++ without a Python runtime.
Native Snippets in Warp Kernels#
Use @wp.func_native to insert native C++/CUDA code
into generated Warp modules. Native functions are useful when Warp does not
provide a built-in operation, CUDA intrinsic, synchronization pattern, or
low-level expression that your kernel needs.
Pure C++ snippets, meaning snippets without CUDA-only constructs, can be used
by CPU kernels. The same snippet can also be used by CUDA kernels if the code is
valid device code. CUDA-specific constructs, such as __shared__ memory,
__syncthreads(), and CUDA atomics, require CUDA kernels.
The decorator takes native source code as a string. The decorated Python
function is a typed stub: its arguments define the names and types available to
the snippet, and its body should be ... because Warp replaces the body with
the native snippet during code generation.
The thread index should be computed by the caller and passed explicitly. Native
snippets are inserted into generated C++/CUDA, so they cannot call
wp.tid() directly.
import numpy as np
import warp as wp
snippet = "out[tid] = x[tid] + 1.0f;"
@wp.func_native(snippet)
def increment(x: wp.array[wp.float32], out: wp.array[wp.float32], tid: int):
...
@wp.kernel
def increment_kernel(x: wp.array[wp.float32], out: wp.array[wp.float32]):
tid = wp.tid()
increment(x, out, tid)
device = "cpu"
x = wp.array(np.arange(4, dtype=np.float32), dtype=wp.float32, device=device)
out = wp.zeros_like(x)
wp.launch(increment_kernel, dim=x.shape, inputs=[x], outputs=[out], device=device)
Inline PTX#
Native snippets can also use inline Parallel Thread Execution (PTX) assembly inside CUDA code. Inline PTX is useful when you need a GPU instruction that is not exposed directly through Warp or CUDA C++.
The following example computes the sum of four byte-wise absolute differences
between two packed 8-bit values. The PTX vabsdiff4 instruction
performs four byte-wise absolute differences and, with the .add modifier,
accumulates them into one 32-bit result.
import numpy as np
import warp as wp
snippet = r"""
unsigned int result;
unsigned int zero = 0;
asm("vabsdiff4.u32.u32.u32.add %0, %1, %2, %3;"
: "=r"(result)
: "r"(a), "r"(b), "r"(zero));
return result;
"""
@wp.func_native(snippet)
def absdiff4_sum_u8(a: wp.uint32, b: wp.uint32) -> wp.uint32:
...
@wp.kernel
def absdiff4_kernel(
a: wp.array[wp.uint32],
b: wp.array[wp.uint32],
out: wp.array[wp.uint32],
):
tid = wp.tid()
out[tid] = absdiff4_sum_u8(a[tid], b[tid])
def pack4(values):
return np.uint32(values[0] | (values[1] << 8) | (values[2] << 16) | (values[3] << 24))
a_host = np.array([pack4([10, 20, 30, 40]), pack4([0, 128, 255, 13])], dtype=np.uint32)
b_host = np.array([pack4([13, 18, 41, 35]), pack4([255, 120, 0, 15])], dtype=np.uint32)
a = wp.array(a_host, dtype=wp.uint32, device="cuda")
b = wp.array(b_host, dtype=wp.uint32, device="cuda")
out = wp.zeros_like(a)
wp.launch(absdiff4_kernel, dim=a.shape, inputs=[a, b], outputs=[out], device="cuda")
# [3 + 2 + 11 + 5, 255 + 8 + 255 + 2]
print(out.numpy().tolist())
np.testing.assert_array_equal(out.numpy(), np.array([21, 520], dtype=np.uint32))
[21, 520]
The "r" constraints bind the operands to 32-bit integer registers, which
matches the .u32 instruction operands. The final PTX operand is an
accumulator and is supplied as a zero-initialized register in this example. If
the assembly reads or writes memory through pointers, add the appropriate
"memory" clobber as described in NVIDIA’s inline PTX documentation.
Returning Values#
A native snippet can return a value when the Python stub declares a return type. Warp supports scalar, vector, matrix, quaternion, array, and fixed-array return types. Struct return values are not supported.
snippet = """
float sq = x * x;
return sq;
"""
@wp.func_native(snippet)
def square(x: wp.float32) -> wp.float32:
...
Pass-by-reference Parameters#
Native functions can use wp.ref[T] parameters for scalar,
vector, matrix, quaternion, or struct values that should be mutated in place.
Inside the native snippet, a wp.ref[T] parameter is visible as a C++/CUDA
reference named after the Python parameter:
@wp.func_native("x = x + 5;")
def add_five(x: wp.ref[wp.int32]):
...
@wp.kernel(enable_backward=False)
def add_five_kernel(values: wp.array[wp.int32]):
i = wp.tid()
add_five(values[i])
Call sites must pass an addressable expression, such as a local variable,
function parameter, array element, struct field, vector or matrix component, or
nested field rooted at an array element. If the native function is used in a
tape-recorded computation, provide an adj_snippet; adjoint variables for
wp.ref parameters use the same adj_ prefix and are also snippet-visible
as references.
Use wp.address_of(expr) when a native snippet needs a
raw pointer to a specific addressable expression instead of a wp.ref[T]
parameter. Use array.ptr for the base pointer of an entire array, and use
wp.address_of(expr) for local variables, array elements, components, or
nested fields such as wp.address_of(v.y) and
wp.address_of(outers[i].inner.value):
@wp.func_native("*(float*)ptr += delta;")
def add_to_ptr(ptr: wp.uint64, delta: wp.float32):
...
@wp.kernel(enable_backward=False)
def add_to_ptr_kernel(values: wp.array[wp.float32]):
i = wp.tid()
local = wp.float32(1.0)
add_to_ptr(wp.address_of(local), wp.float32(2.0))
add_to_ptr(wp.address_of(values[i]), local)
Raw pointer writes performed through native snippets are not automatically differentiable by Warp. Keep those kernels forward-only or provide the appropriate manual adjoint for the surrounding native operation.
Differentiable Native Functions#
When a native function participates in a tape-recorded computation, provide an
adj_snippet that accumulates adjoints for the native operation. Adjoint
variables use the adj_ prefix, and return-value adjoints are named
adj_ret.
snippet = "out[tid] = 2.0f * x[tid] + y[tid];"
adj_snippet = """
adj_x[tid] += 2.0f * adj_out[tid];
adj_y[tid] += adj_out[tid];
"""
@wp.func_native(snippet=snippet, adj_snippet=adj_snippet)
def axpy(
x: wp.array[wp.float32],
y: wp.array[wp.float32],
out: wp.array[wp.float32],
tid: int,
):
...
During the backward pass, Warp runs a forward replay phase. By default, native
functions replay the original snippet. If the forward snippet has side
effects that should not be repeated, such as mutating a counter with an atomic
operation, provide replay_snippet. An empty string is a valid no-op replay
snippet.
snippet = """
int next_index = atomicAdd(counter, 1);
thread_values[tid] = next_index;
"""
replay_snippet = ""
@wp.func_native(snippet, replay_snippet=replay_snippet)
def record_index(counter: wp.array[int], thread_values: wp.array[int], tid: int):
...
Native Function Limitations#
Native snippets are inserted into generated C++/CUDA and are not parsed as Warp code.
The snippet can refer to variables named after the typed Python stub arguments.
CUDA-specific snippets cannot run on CPU devices.
Type hints must accurately describe the stub arguments and return type.
Struct return values are unsupported.
Users are responsible for native-code correctness, synchronization, memory safety, and portability.
External Build Inputs#
Experimental
The external-compilation APIs described in this section are experimental and may change without deprecation in future releases.
Extra build inputs for a module can be grouped in
wp.ModuleBuildOptions:
build_options = wp.ModuleBuildOptions(
extra_cuda_include_dirs=[my_cuda_include_dir],
extra_cpu_include_dirs=[my_cpu_include_dir],
extra_cuda_preamble='#include "my_cuda_addon.h"',
extra_cpu_preamble='#include "my_cpu_addon.h"',
extra_build_dependencies=[my_cuda_addon_header, my_cpu_addon_header],
)
wp.set_module_options({"extra_build_options": build_options})
The preambles are inserted after Warp’s own headers, but before codegen-only
cast macros and the generated code. An external header may therefore use public
Warp macros such as CUDA_CALLABLE and ordinary C++ function-style casts,
and the generated kernels see everything the preamble declares. A preamble
cannot define macros that Warp’s headers consume, because those are already
included.
Warp hashes the configured include-directory paths, but not the contents of
headers resolved through them. List external headers and other build inputs in
extra_build_dependencies so Warp also hashes their contents. Dependency
contents are re-read whenever the module hash is recomputed — after
wp.set_module_options() or in a new process — not on every launch.
Changes to a wp.ModuleBuildOptions instance
take effect when it is passed to wp.set_module_options(). After modifying an instance, call
wp.set_module_options() again so Warp invalidates the module’s cached
compilation state.
If more than one addon contributes build inputs, combine them without discarding earlier settings:
current = wp.get_module_options(kernel.module)["extra_build_options"]
if current is None:
current = wp.ModuleBuildOptions()
wp.set_module_options(
{"extra_build_options": current.merged(addon_build_options)},
module=kernel.module,
)
External Native Value Types#
External C++ value types can be used directly as kernel annotations, built-in
arguments and return values, Warp struct fields, and array dtypes. Define the
host ABI with ctypes.Structure and register the same class:
import ctypes
import warp as wp
class Color(ctypes.Structure):
_fields_ = [
("r", ctypes.c_float),
("g", ctypes.c_float),
("b", ctypes.c_float),
]
wp.build_experimental.add_native_type(
Color,
native_name="render::Color",
fields={"r": wp.float32, "g": wp.float32, "b": wp.float32},
initializer="aggregate",
)
The exact C++ definition must be made visible using the module preamble and
include-directory options above. Warp emits compile-time checks for standard
layout, trivial copyability, size, and alignment. CPU and CUDA compilation
also check each exposed member’s C++ type. CPU compilation checks every exposed
field offset, while CUDA compilation checks each field size. Setting
initializer="aggregate" opts into ordered field construction such as
Color(r, g, b) inside kernels. In this mode, fields must contain every
ctypes field in declaration order. Without it, only default construction is
available.
Pass fields=None (the default) for an opaque type. Opaque values can cross
kernel and registered built-in boundaries and can be stored in arrays, but
their members and captured constants are not exposed. Warp does not manage
resources referenced by opaque values; the external package remains
responsible for their ownership and lifetime.
Native value types do not automatically gain arithmetic or differentiation.
Register operations explicitly with warp.build_experimental.add_builtin(), and use
arrays without requires_grad.
Complete Addon Example#
An addon should register its types and built-ins when imported, then expose a
small helper that applies its build inputs to the module containing the user’s
kernels. For example, my_addon.py can contain:
import ctypes
from pathlib import Path
import warp as wp
include_dir = Path(__file__).parent / "include"
header = include_dir / "my_addon.h"
class Color(ctypes.Structure):
_fields_ = [
("r", ctypes.c_float),
("g", ctypes.c_float),
("b", ctypes.c_float),
]
wp.build_experimental.add_native_type(
Color,
native_name="my_addon::Color",
fields={"r": wp.float32, "g": wp.float32, "b": wp.float32},
initializer="aggregate",
)
wp.build_experimental.add_builtin(
"my_addon_scale_color",
{"value": Color, "factor": wp.float32},
Color,
native_name="my_addon::scale_color",
)
_build_options = wp.ModuleBuildOptions(
extra_cuda_include_dirs=[include_dir],
extra_cpu_include_dirs=[include_dir],
extra_cuda_preamble='#include "my_addon.h"',
extra_cpu_preamble='#include "my_addon.h"',
extra_build_dependencies=[header],
)
def configure_module(module):
current = wp.get_module_options(module)["extra_build_options"]
if current is None:
current = wp.ModuleBuildOptions()
wp.set_module_options(
{"extra_build_options": current.merged(_build_options)},
module=module,
)
Users import the addon before compiling kernels that call its built-ins, and configure each kernel module before its first launch or AOT compilation:
import warp as wp
import my_addon
@wp.kernel
def scale_colors(colors: wp.array[my_addon.Color], factor: float):
tid = wp.tid()
colors[tid] = wp.my_addon_scale_color(colors[tid], factor)
my_addon.configure_module(scale_colors.module)
# A normal wp.launch() now JIT-compiles with the addon's build inputs.
# For external runtimes, AOT compilation returns the generated artifact.
artifacts = wp.compile_aot_module(
scale_colors.module,
device="cuda",
use_ptx=True,
)
Registered built-in names share Warp’s global kernel namespace. Addons should use a package-specific prefix unless they intentionally add an overload to an existing Warp operation. Registrations are process-global and equivalent registrations are idempotent, which makes normal module reloads safe.
Ahead-of-Time C++/CUDA Workflows#
Warp can compile kernels ahead of time and write the generated CUDA source, metadata, PTX, or CUBIN files to disk. This is useful when a Python build step authors and validates kernels, but a native CUDA C++ application owns runtime execution.
The full AOT workflow is documented in
Ahead-of-Time Compilation Workflows. The C++ examples under
warp/examples/cpp/ show two deployment patterns:
00_cubin_launch compiles a Warp kernel to a CUBIN, loads that module with the CUDA Driver API, and launches the generated kernel with
cuLaunchKernel().01_source_include includes the generated
.cusource in a CUDA C++ translation unit and launches the generated forward and backward kernels directly.
Both examples use warp/native/aot.h for Warp’s generated type definitions,
CUDA setup helpers, and error-checking macros. The generated code also depends
on the native type headers such as builtin.h that ship in warp/native/.
API Capture Replay from C++#
API Capture (APIC) can serialize a captured Warp graph to a .wrp file plus a
companion module directory. The saved graph can later be loaded from Python or
from a standalone C++ program that links against the Warp native library.
See API Capture: Saving and Loading Graphs for the C API surface, serialization format notes, and current limitations. The C++ examples cover both device families:
02_apic_visualization records and saves a CUDA workload in Python. The C++ loader reconstructs a CUDA graph, updates named inputs, and replays the frame with
cudaGraphLaunch().03_apic_visualization_cpu captures and replays on the CPU device. The C++ viewer does not link against CUDA. It loads recorded CPU kernel objects and replays the graph with
wp_apic_cpu_replay_graph().
Native Library Headers#
The C++ integration examples intentionally use a small native surface:
warp/native/aot.hexposes utilities for generated AOT kernels and includes Warp’s generated type support.warp/native/warp.hdeclares the core Warp C API exported by the native library.warp/native/apic.hdeclares the APIC graph loading and replay API used by.wrpgraph consumers.
Other files in warp/native/ implement Warp’s runtime and kernel support
library. They are useful when inspecting generated code, but the examples above
are the recommended starting points for native host integration.