This chapter is a practical companion to CONTRIBUTING.md, which states the project policy (compatibility, commit format, pull-request requirements). Here we cover the development loop, the test suites, and how to add functionality.
git clone https://github.com/latteine1217/quabla.git
cd quabla
python3 -m venv .venv && source .venv/bin/activate
python -m pip install "maturin>=1.9.3,<2" numpy ruff
maturin develop --release # add --features mlx or --features cuda
The package is a mixed Rust/Python project. maturin develop installs
python/quabla/ in editable mode and writes the compiled extension next to
it as python/quabla/_quabla.*.so, so:
python/quabla/*.py take effect immediately;maturin develop --release again.If quabla.__file__ does not point into python/quabla/ after a build
(for example because an older wheel is installed), run
python -m pip uninstall quabla and build again.
Run the gates that cover your change before opening a pull request; the complete list is in CONTRIBUTING.md. In short:
cargo fmt --all --check
ruff check tests examples python
cargo clippy --workspace --all-targets -- -D warnings
cargo test --workspace
python tests/python/test_api.py # and the other tests/python/*.py files
Each Python test file is a standalone script: it runs every test_*
function and prints PASS <name>. Run one file directly while iterating.
GPU suites are opt-in, because CI has no GPUs:
QUABLA_MLX_TEST=1 python tests/python/test_api.py # MLX build on Apple silicon
QUABLA_CUDA_TEST=1 python tests/python/test_api.py # CUDA build
QUABLA_CUDA_TEST=1 cargo test -p quabla-core --features cuda
QUABLA_CUDA_NCCL_TEST=1 cargo test -p quabla-core --features cuda-nccl # two GPUs
cargo test --workspace --features quabla-core/mlx # Rust MLX tests (macOS)
CUDA code only compiles on Linux. If you change a GPU backend without the hardware to test it, say so in the pull request.
| Location | Covers |
|---|---|
crates/quabla-core/tests/tensor_ir.rs |
The main IR suite: ops, AD, regions, batching, the compiler facade, device parity |
crates/quabla-core/tests/tensor_ir_linalg.rs |
Batched solve and decompositions, their AD and batching |
crates/quabla-core/tests/compiler_liveness.rs, cpu_execution_memory.rs |
Folding, CSE, pruning, buffer liveness and reuse |
crates/quabla-core/tests/cpu_loop_checkpoint.rs |
Checkpointed loop VJP is bit-identical to the full tape |
crates/quabla-core/tests/cuda_*.rs |
CUDA-only: codegen size, float64, Cholesky, retained losses, solve profiles |
crates/quabla-core/src/tensor_ir/*_tests.rs |
In-crate unit tests (region batching, MLX checkpoints, Cholesky) |
tests/python/test_api.py |
Public API, exports, operations, AD |
tests/python/test_tensor_api.py |
Eager Tensor operations, dtypes, and the compiler facade |
tests/python/test_devices_api.py, test_lower_program.py |
Device jit and ahead-of-time lowering |
tests/python/test_control_api.py, test_custom_api.py |
Control flow; custom rules |
tests/python/test_optim_api.py, test_linalg_api.py, test_solvers_api.py, test_ode_api.py, test_random_api.py, test_numpy_api.py |
The libraries |
tests/python/test_packaging_api.py |
Version, exported names, the type stub |
Most new functions do not need a new IR op. Choose the lowest-cost layer that gives correct values and derivatives:
python/quabla/_ops.py, linalg.py, …).
If the function can be written with existing operations, write it in
Python. It then works eagerly, under every transform, and on every
backend with no Rust change. square, hypot, logaddexp, softmax,
pad, interp, and most of qb.linalg are compositions.qb.custom_vjp/custom_jvp in Python). If the
composition is right but its automatic derivative is unstable or
expensive, wrap it. The Krylov solvers and Newton’s method work this way.
The rule also applies inside cond and loop bodies.Follow the conventions of _ops.py:
def square(x):
"""Elementwise `x * x`."""
x = _array(x)
return x * x
_array, _operands) so that
numbers, lists, and NumPy arrays work, and Python scalars stay weak.Tensor and TraceTensor.hypot does.stop_gradient for scaling factors that should not change the
gradient, so the derivative stays exact.__all__, and avoid shadowing Python builtins in
__all__ (see how sum and max are handled).A native op must be implemented end to end: every transform and every backend, or an explicit rejection.
An elementwise math function of one or two operands is a new kind of
UnaryMathKind or BinaryMathKind in tensor_ir/elementwise.rs, not a new
op. Adding the variant makes the compiler ask for every rule of the
function there (name, f64 value, symbolic derivatives, CUDA
spelling) and for its MLX lowering in mlx_unary_math or
mlx_binary_math; the questions with a default (CUDA fusion and loop
admission, folding, StableHLO) are answered next to them. Nothing else in
steps 1 to 8 below changes; the Python surface and the tests (steps 9 to
14) still do.
Core IR (crates/quabla-core/src/tensor_ir.rs):
TensorOp variant.TensorIr that computes the output shape and dtype.evaluate_tensor_nodes_with_outputs, computed in f64
and rounded through TensorDType::round.symbolic_jvp_many_with_seed) and symbolic VJP rule
(symbolic_vjp_many_impl), written with IR ops so higher orders work.push_batched (and specialize_mapped_axis_zero).tensor_op_inputs,
remap_tensor_op, tensor_op_name, lower_text, constant folding
(fold_scalar_constant_op, fold_tensor_constant_op), the CSE key
(pure_tensor_op_cse_key; include every attribute that distinguishes two
instances), placement inference, and buffer reuse.Backends:
tensor_ir/cuda.rs): per-node launch, the elementwise formula
that every CUDA generator shares (cuda_elementwise_formula in
tensor_ir.rs), fusion
admission (is_fusable_elementwise_compute_op), loop-body lowerability,
and the float64 forms.tensor_ir/mlx.rs): lowering in lower_arrays_with_retained and
the op name. If MLX lacks the function, implement it from MLX ops within
a stated accuracy (as cbrt and erfc are) or reject it explicitly.Python:
Tensor (crates/quabla-python/src/tensor.rs) and
the traced method on TraceTensor
(crates/quabla-python/src/tensor_trace.rs).python/quabla/_ops.py.python/quabla/_quabla.pyi; test_packaging_api.py fails
while an exported name is missing from it.Tests and docs:
crates/quabla-core/tests/tensor_ir.rs: values against
a reference, derivatives against finite differences or a closed form,
every order you claim, and batching.tests/python/test_api.py and device parity in
tests/python/test_devices_api.py.docs/api.md (contract and numerics), CHANGELOG.md, and where it
applies docs/jax_like_roadmap.md.Region-level code (region_batching.rs, cuda_host_loop.rs) works on whole
regions and normally needs no per-op change.
DeprecationWarning for at least one minor release before the next major
release removes it. Backends, the IR, and Rust APIs are not covered and
may change.python/quabla/_errors.py rather
than inventing a new one; each subclasses the builtin exception that
existing handlers catch.crates/quabla-python/Cargo.toml.docs/api.md is the contract: exact signatures, numerical methods,
derivative conventions, and backend differences. This guide teaches usage
and links to it.