Add an execution backend#
An execution backend is the library that lets eagle run on one kind of device: its streams, graphs, conditional nodes
and events. There is one file per kind, libeagle_<kind>, and eagle’s Python core names no kind in its logic. This
page is what you write to add one (HIP, Metal, OpenCL), and what eagle does with it.
The seam: eagle-backend/1#
The seam is a C header, python/src/seam/eagle_backend.h: plain C, structs of plain data, nothing from C++
crosses it. A backend exports functions with the eagle_backend_ prefix.
Seven symbols are required of every backend:
Symbol |
It answers |
|---|---|
|
the seam version it was built from ( |
|
the message of the last failed call on this thread |
|
its kind, the eagle release it was built from and the device code it carries |
|
whether a usable device is present |
|
frees a buffer the backend allocated |
|
which optional groups it provides, one bit each |
|
the DLPack device types it serves |
The rest are optional, in groups. A backend sets one capability bit per group it provides, and a set bit promises
that group’s symbols: a missing one is a load refusal that names it. A group the backend leaves out makes its calls
raise eagle.BackendUnavailable("... does not provide <group>"), so a backend can start small.
Group |
What it gives eagle |
|---|---|
|
create, synchronize and release a stream |
|
record the work on a stream into a replayable graph |
|
branches that run at the same time inside a capture |
|
|
|
which captured node belongs to which launch |
|
build a graph node by node, then replay it |
|
launch a compiled kernel over a range of lanes |
|
device properties |
|
fences for DLPack hand-offs, memory copies |
|
compaction and reorder for |
The header holds the exact symbol list of every group. Within major version 1 the seam only grows: a new symbol is
bound by itself, and its presence is the capability. A new symbol bumps EAGLE_BACKEND_VERSION_MINOR.
Minor 6 adds four optional symbols, bound one by one: eagle_backend_memcpy (host to device, device to host, device to
device and host to host, on a stream), eagle_backend_host_alloc and eagle_backend_host_free (page-locked host memory)
and eagle_backend_host_func (run a host function in stream order, which a captured graph keeps as a host node). A
backend without them still loads; eagle.copy, eagle.pinned and host nodes then raise BackendUnavailable naming the
missing symbol. A backend on hardware that shares one memory between host and device may make every copy a no-op: that
choice stays inside the backend.
The kind and the file#
A kind is an execution model (cuda, hip, metal), never a toolkit generation: a CUDA 13 build is still kind
cuda. The backend of kind K is the file libeagle_K.so (.dylib on macOS, .dll on Windows) beside the eagle
package. $EAGLE_BACKEND_K names an exact file to load instead, and a failure to load it is an error, not a fall-back
to another file.
How eagle finds the backend#
An array tells eagle where it lives. The kind comes from its DLPack device type: host memory is the host, type 2 is
cuda, 8 is metal, 10 is hip. So an array from CuPy, torch or JAX picks its backend without eagle knowing
the framework.
backend="gpu" in Graph.build means the one GPU kind installed on the machine. If two are installed, eagle refuses by
name and asks you to say which:
eagle: backend='gpu' is ambiguous, cuda, hip are installed; name one of them as the backend
Pass backend="hip" (or any kind) to name it. The default, when you pass nothing, is unchanged: the backend is
inferred from where the planes live.
The C++ graph: ExecGraph#
In C++ the graph executor is eagle::Graph, an alias for the one of this build: eagle::cpu::Graph under
EAGLE_CPU_ONLY, eagle::cuda::Graph otherwise (eagle/ExecGraph.h). Both satisfy the ExecGraph concept: move-only,
a scratch arena, the native-node dependency table, and finalize() to commit the arena. Code written against
ExecGraph builds for either, at no runtime cost, and a new executor (hip::Graph) is added by satisfying the same
concept; a static_assert beside the alias checks it.
Checklist#
Write the seven required symbols and set the capability bits for the groups you provide.
Build
libeagle_<kind>and put it beside the package, or point$EAGLE_BACKEND_<KIND>at it.Add the kind to the DLPack table in
python/eagle/_device_kind.pyif it is not there.Pin the symbols the core binds in
python/tests/expected_backend_symbols.txt, and run the backend wall test.Add the target on the hawk side: Add a target. The format and entry kind the target writes are in raptor’s manifest vocabulary.
The core loads one kind per process today: the binding’s default is a single named constant, and
_core.cuda_backend(kind=...) takes another. Taking the kind per call comes when a second GPU kind exists.