The v2 execution axis (aether-abi/2) and eagle’s Python plan/exec API#
Schema version 2 adds one new axis to the manifest: which execution
structure(s) — a single device kernel launch, an OpenMP team of CPU
threads, an MPI rank partition, and (planned) an NCCL device group —
may drive a plugin body, and how that body is allowed to touch data outside
its own sample. A schema-v2 manifest tags itself "schema_version": 2 and
"aether_abi": "aether-abi/2", and MUST additionally carry three new
top-level, flat keys (absence is a load refusal, never a silent fallback to
v1 behaviour):
Key |
Meaning |
|---|---|
|
A non-empty list drawn from |
|
How the body touches data: one of |
|
The reduction operator ( |
A v2 artifact’s entry points additionally take an explicit partition
triple {base, count, nSamples} after their ordinary role arguments —
eagle, never the plugin, decides how a run is split and drives the same
body once per partition (a device kernel launch, or one CPU thread team’s
tile). The accum_out role (see The arg_spec role vocabulary) names the cross-sample
accumulate plane a cross_sample_write body writes into.
Legacy v1 artifacts are unaffected. A schema_version: 1 /
aether_abi: "aether-abi/1" plugin carries none of the three keys above
(carrying one is itself a strict-key violation) and is always run
whole-view, on a single structure — precisely today’s behaviour, byte for
byte. eagle.abi.check_aether_abi accepts both tags; only a v2-tagged
artifact may be split across more than one partition.
Placement legality is a function of exec_access and the chosen
structure alone, and is refused at plan time, naming the rule, never
guessed or discovered mid-launch: sample_local / cross_sample_read /
mapreduce run under any implemented structure; cross_sample_write
runs on a single-device structure only (splitting it across partitions would
need a partial-accumulate combine step not yet specified) — the same
plain-language rule eagle.exec.check_placement() enforces mechanically.
Driving a v2 body from Python — eagle.plan / eagle.exec#
eagle.exec is the execution-structure vocabulary: the
DeviceKernel / HostTeam /
RankPartition / DeviceGroup
singletons, plus check_placement() and
check_layout_sizes() (a self-check: a v2 artifact’s
exported eagle_layout_sizes must agree with this build’s own POD sizes,
or the load is refused naming the disagreeing field — a matching
aether_abi tag alone cannot catch a stale or wrong-arch binding).
eagle.plan is where a run is PLANNED: eagle.plan.plan() maps a
plugin, a chosen structure, and an explicit partitioning to a Plan, and
Plan.run(**kw) drives it:
import eagle.exec as eexec
from eagle import plan as eplan
# Selection is always EXPLICIT (no residency-driven auto-choice yet) --
# the caller names the structure every time.
whole = eplan.plan(my_v2_kernel, structure=eexec.DeviceKernel).run(x=x, a=2.0, b=-1.5)
# Splitting a `sample_local` body into partitions never changes the
# per-sample answer -- eagle drives the identical body per partition.
split = eplan.plan(
my_v2_kernel,
structure=eexec.DeviceKernel,
partitions=[(0, 32, 64), (32, 32, 64)],
).run(x=x, a=2.0, b=-1.5)
# The same body under HostTeam (OpenMP) instead of DeviceKernel (CUDA):
host = eplan.plan(my_v2_kernel, structure=eexec.HostTeam).run(x=x, a=2.0, b=-1.5)
# An illegal placement is refused at plan() time, naming the rule:
eplan.plan(
my_v2_kernel, structure=eexec.DeviceKernel,
partitions=[(0, 32, 64), (32, 32, 64)], exec_access="cross_sample_write",
) # -> ValueError: "... exec_access 'cross_sample_write' is legal on
# single-device structures only ..."
partitions= is a list of explicit (base, count, n_samples) triples
(contiguous, covering [0, n_samples)); omit it (with npartitions=
left at its default of 1) for a deferred whole-view run, whose size is
resolved from the arrays passed to .run(). See
eagle/python/tests/test_exec_contract_rows.py for the certification rows
this API is held to (bit-exact partition identity for a sample_local
body, a fixed host/device tolerance band, and the two refusal cases above).
Running across MPI ranks — RankPartition#
RankPartition is the multi-process structure. Rank r of
R runs the r-th contiguous sub-partition of one whole partition
through an inner structure — HostTeam or DeviceKernel — carrying
its own {base, count, nSamples} triple, with nSamples unchanged: a
rank is one more cut of the same run, not a smaller run. Inputs are
replicated (which is why a cross_sample_read body is legal here: the
tables it reads are whole on every rank), and outputs are gathered, so
every rank ends holding the whole plane, bit-identical to a single whole run.
cross_sample_write stays refused — two ranks accumulating into one shared
target would need a partial-accumulate combine that is not specified.
# The plan's partitions stay WHOLE partitions: the rank cut happens INSIDE
# the structure, so the same plan describes the run at any world size.
whole = eplan.plan(
my_v2_kernel, structure=eexec.RankPartition, inner=eexec.HostTeam,
).run(x=x, a=2.0, b=-1.5) # every rank gets the WHOLE output plane
rank, size = eexec.RankPartition.world() # (0, 1) outside a launcher
mine = eexec.RankPartition.local(eexec.Partition.whole(len(x)))
A mapreduce body folds its own rank’s partials and then combines the R
partials in a fixed rank order
(allgather_fold()) — never an
MPI_Allreduce, whose combine order is the implementation’s business, so
two ranks could disagree in the last bits.
This structure lives in a separate, optional compiled extension
(eagle._mpi, built with -DEAGLE_PYTHON_MPI=ON; AUTO, the default,
builds it wherever CMake finds an MPI). eagle._core therefore never links
libmpi, and import eagle costs nothing to a user with no MPI
installed; where the extension is absent, every RankPartition call
refuses with a RuntimeError naming that build switch. Note that
initialising MPI is not a passive act — Open MPI installs process-wide memory
hooks — so drive this structure from a process dedicated to it (an mpirun
world, or a short-lived child), not from inside a long-running session that
is also using CUDA. The certification rows are
eagle/python/tests/test_rank_partition.py (single process) and the
two-rank bed eagle/python/tests/mpi/ (run through its own gate,
tests/mpi/check_rank_bed.sh).