Adding an operator
Every file that has to change, derived from the commit that added
erfc — the cheapest operator this project can have.
What it costs
erfc is a unary function that slots into an existing shader's switch.
It needs no new dispatch path, no new push-constant block and no new memory pattern. It touched
15 files and 145 lines. Anything harder touches strictly more.
That number is worth knowing before starting, and it is the reason the project's roadmap puts reducing this cost ahead of adding features.
The fifteen files
Identity — the graph's vocabulary
| File | What to add |
|---|---|
include/vkml/graph/op.h |
An OpKind enumerator. Append it — do not insert. The value
reaches the shader as a specialisation constant, so renumbering changes what an already
cached pipeline computes. |
src/graph/op.cpp | Its name, for errors and the coverage report. |
Public API
include/vkml/api/ops.h |
The declaration, and a /// block explaining anything non-obvious about it.
That block is rendered on this site, so it is documentation rather than
a comment. |
src/api/ops.cpp |
Shape and dtype checks, then the node. Compose from existing operators if you can — most losses do, and they needed no new kernel on either backend as a result. |
Kernels — both backends
src/backend/cpu/kernels_*.cpp |
The CPU implementation. Required. CPU support must be a superset of Vulkan support, and a test enforces it — a GPU-only operator has no oracle. |
shaders/*.comp |
The GLSL. Add an OP_ constant and a case, or a new shader if the
memory pattern differs. |
src/backend/vulkan/vulkan_backend.cpp |
The supports() entry and the dispatch. If the operator cannot run on the GPU,
leave it out of supports() — it will then raise
NotImplementedError, which is the designed behaviour rather than a
gap. |
Gradient
src/autograd/autograd.cpp |
A case building the gradient from forward operators. A
dedicated backward kernel needs an argument for why the gradient genuinely cannot be
composed — only four operators have earned one, and all four are scatters. |
Python surface
bindings/module.cpp | The nanobind binding. |
python/vkml/__init__.py | The re-export. |
Tests and policy
tests/python/tolerance.py |
A tolerance entry with a citable justification, not a number that makes the test pass. Which kind — exact, ULP, relative or backward — is itself the argument. |
tests/python/test_ops_vs_torch.py | Agreement with PyTorch. |
tests/python/test_vulkan_kernels.py | Agreement with the CPU oracle. |
tests/python/test_invariants.py |
Anything the operator promises that a value comparison cannot express — NaN behaviour, a bound, an exactness claim. |
docs/coverage-baseline.json |
Regenerate only after confirming the run has no new gaps. Writing a baseline from a failing run accepts the regression it was meant to catch. |
Documentation
The site's implementation table, backend chips and cross-links are generated, so a new
operator appears automatically. What is not generated is what it does: add an entry to
the matching file in web/content/, and the build will report the coverage
percentage.
Examples are executed by a gate. Paste what the interpreter printed — that
gate's first run found 17 invented outputs, including .shape written as a list when
it returns a tuple.
Before pushing
ctest --preset release
python -m pytest tests/python -q
VKML_MIN_SPEC=1 python -m pytest tests/python -q
python scripts/check_layering.py
python scripts/check_push_constants.py
python scripts/check_cpu_only_build.py
python scripts/check_docs_examples.py
python scripts/check_docs_references.py
python scripts/check_versions.py
python scripts/coverage_matrix.py
docs/PRE-COMMIT-CHECKLIST.md is the authority, and every item on it exists
because something got through without it.
Two habits the project treats as standard
- Red-verify. Break the thing your new test guards and watch it fail. A test that has never been seen to fail is not yet evidence.
- Say what you did not verify. A commit message here records what was measured, what was assumed, and what could not be checked — a claim without that is harder to trust than an admission.