Development
August 22, 2026 ยท View on GitHub
How to build, test, and extend the aneforge package.
Building
The package is an editable install with a numpy-only core. The one native
artifact it needs at runtime is the e5rt dispatch shim, compiled with
xcrun clang++ from source shipped in the package.
pip install -e . # core: numpy only
python -m aneforge.build # build aneforge/_lib/libane_e5rt_dispatch.dylib (optional)
The shim compiles aneforge/_lib/ane_e5rt_dispatch.mm against Apple's frameworks,
so it must build on the target Mac. It builds automatically the first time you
dispatch to the ANE (cached next to the source, or in ~/.cache/aneforge/<version>/
if the package tree is read-only); python -m aneforge.build does it ahead of time,
and sh aneforge/_lib/build.sh still works for an in-tree build. The resulting
.dylib is a build artifact (not tracked); it rebuilds after a pull that touches
ane_e5rt_dispatch.mm. It loads lazily, so import aneforge works without it;
only compiling or dispatching to the ANE needs it. Set ANEFORGE_NO_AUTOBUILD=1
to require an explicit build.
Python environment
Use a persistent virtualenv in the repo. Python 3.12 has the widest wheel coverage for the optional extras.
python3.12 -m venv .venv # .venv/ is gitignored
.venv/bin/python -m pip install -e ".[dev]" # ruff + pytest (lint/test)
# optional, for the pretrained-model and diffusion demos (lazy imports):
.venv/bin/python -m pip install -e ".[models]" # torch, torchvision, transformers
Pre-commit hook (enable once per clone)
A committed hook at .githooks/pre-commit runs the off-hardware checks
(ruff check + compileall aneforge) before every commit; it is dependency-free.
Git does not auto-trust committed hooks, so enable it once after cloning:
git config core.hooksPath .githooks
The hook does not run the corpus or pytest (those need ANE hardware and gate
separately). Bypass one commit with git commit --no-verify.
Tests
tests/run_corpus.py is the standing correctness gate: operator graphs
compiled and run on the ANE and compared against numpy references at fp16.
It must be green before a release.
PYTHONPATH=. .venv/bin/python tests/run_corpus.py # the corpus gate
PYTHONPATH=. .venv/bin/python tests/op_smoketest.py # per-op smoke test
PYTHONPATH=. .venv/bin/python -m pytest tests/ -q # full suite
The pytest suite runs each test in its own forked subprocess. Every compile
allocates an ANE program, and a single long-lived process accumulates them
across the whole suite, approaching the per-process program limit and causing
sporadic failures late in a run; a fresh process per test avoids that.
tests/conftest.py sets the environment the forked path needs, so a plain
pytest tests/ works.
Adding an operator
Operator coverage grows by mapping a MIL spelling to a Python op. The path:
- Catalog it. Add the op to
aneforge/_op_catalog.py(the single source of truth for per-device availability), then regenerate the reference table withpython docs/gen_op_catalog.py > docs/op-catalog.md. - Lower it. Add the fused-MIL emitter in
aneforge/_compile.py, or, for an op MIL rejects but the hardware supports, add a netplist bridge underaneforge/_bridges/. - Expose it. Add the method on the graph type in
aneforge/graph.py. - Test it. Mirror an existing case: compile, run on the ANE, compare against a numpy reference at fp16. Add it to the corpus.
Code style
The package targets Python 3.10+ and is linted with ruff (the pre-commit hook
runs ruff check). Match the surrounding style.
Contributing
Useful contributions:
- Operator coverage gaps (anything in capabilities.md marked as not yet covered).
- Bug reports for shape combinations that compile but produce wrong output (CPU/ANE divergence at boundary fp16 values is real).
Propose larger architectural changes in an issue first.
License
MIT. The framework symbols aneforge calls are Apple's private API, under no
license; they may change without notice, and nothing here is an API contract.