Skip to content

refactor: ♻️ adopt the array API standard for backend dispatch and array typing - #69

Merged
Panadestein merged 4 commits into
mainfrom
refactor/array-api
Oct 15, 2026
Merged

Panadestein merged 4 commits into
mainfrom
refactor/array-api

Conversation

@Panadestein

Copy link
Copy Markdown
Member

🤖 AI text below 🤖

Closes #45.

What changes

  • Namespace from the arrays. The algorithms take the array namespace from their inputs with array_api_compat.array_namespace (truncated_qr, the out-of-memory splitting in _sweep, EnvironmentStore). Nothing outside utils/_backend.py takes an xp parameter any more.
  • One place picks the backend. DeviceGroup(device, devices) resolves the namespace from the public device= string and the device ids from Resources.devices, so resolve_devices folds into it. resolve_budgets(resources, group), SiteSource(..., group=group) and sweep(..., device=...) follow. EnvironmentStore no longer needs a backend: it follows the arrays it is given, and allocates its page-locked rings on the first staged put.
  • One array type. src_method.utils.Array is the documented alias for an array of the active backend (numpy.ndarray or cupy.ndarray). CuPy ships no stubs, so it is Any to a type checker. Host-only arrays (the results of src/apply/compress, the exact two-site path, the sketches) are now annotated np.ndarray, and no NDArray remains on backend-generic arrays.
  • Standard names. Array creation, linalg.qr, conj, reshape, moveaxis, concat and asarray use the standard API. truncated_qr is shorter and keeps the SVD of R explicitly on the host.
  • The seam keeps the rest: streams and events, page-locked memory, CuPy's pool, peer copies, and gaussian_sketch, which now always returns a host array (it was only ever called with NumPy).
  • array-api-compat>=1.12 becomes a runtime dependency. The docs (getting started, contributing, dependencies) and AGENTS.md describe the new convention.

Not done, on purpose:

  • from_dlpack: there is no zero-copy exchange to make. Host→device and device→host are real copies, and to_numpy stays the single device→host transfer.
  • Non-standard calls kept: ascontiguousarray (the scatter needs C-contiguous blocks) and the byte-level .view(dtype) of the staging buffers have no standard equivalent.
  • CUDA calls in the seam reach CuPy through xp.cuda, which array_api_compat.cupy re-exports.

Tests

On Leonardo Booster (4× A100-64GB, CUDA 13 via forward compat):

  • pytest (the whole suite, slow and GPU tests included): 265 passed, 0 skipped, including the two-GPU tests.
  • pytest -m "not slow" at the lowest direct versions (Python 3.11, array-api-compat 1.12.0, NumPy 2.0.0, opt_einsum 3.4.0): 245 passed, 1 skipped (the CuPy module).
  • prek run --all-files: clean.

Performance

origin/main vs. this branch, in the same job, alternating processes. Each case was run 5–7 times per process, over 2–3 rounds, and the table gives the best per implementation. Outputs agree bit for bit.

Case (complex128) main branch Δ
GPU MPO·MPO, 25 sites, χ=1000 1.828 s 1.826 s −0.1%
4 GPUs, same 1.469 s 1.462 s −0.5%
1 GPU, gpu_memory="1GB" (environments on the host tier) 2.620 s 2.629 s +0.3%
GPU MPO·MPO, 50 sites, χ=50 0.153 s 0.156 s +2.0% (noise, see below)
GPU depth-3 stack (2 MPOs χ=8 + MPS χ=64), 30 sites, χ=256 0.148 s 0.149 s +0.7%
CPU MPO·MPO, 50 sites, χ=50 0.232 s 0.231 s −0.4%
CPU MPO·MPO, 20 sites, χ=200 0.926 s 0.933 s +0.8%
CPU depth-3 stack, 30 sites, χ=256 13.939 s 13.907 s −0.2%

Single rounds of the CPU depth-3 stack differ by up to 3% between processes, whichever code runs. A rerun with the branch first gave 14.770 s (branch) against 14.802 s (main), and the per-pass sweep timings match.

These cases are dominated by per-call overhead, so I also timed them at steady state (30 back-to-back calls, two rounds):

Case main branch
GPU depth-3 stack, 30 sites, χ=256 0.117 / 0.118 s 0.118 / 0.117 s
GPU MPO·MPO, 50 sites, χ=50 0.1272 / 0.1249 s 0.1261 / 0.1268 s

The compat wrappers cost a few µs per call on CuPy once warm. A first version paid about 1.5 ms per small sweep for array_namespace and is_numpy_namespace calls in the per-batch path. Those now run only where they are needed, and is_host is an identity check again.

pytest-benchmark (min, ms):

Benchmark main branch
test_benchmark_src_stack_depth3 60.9 58.5
test_benchmark_src_mpo_mpo 1711 1693

Size

Python lines: 8344 → 8334 (src: 3555 → 3550). Whole diff, docs and lockfile included: +289 / −281.

…ray typing

The algorithms take the array namespace from their arrays through
array-api-compat instead of receiving an `xp` module. The namespace is
picked once from `device=` by `DeviceGroup`, which now also resolves the
device ids; `EnvironmentStore` follows the arrays it is given, and
`truncated_qr` and the splitting kernels follow their inputs.

Arrays of either backend are annotated with one alias,
`src_method.utils.Array`; host-only arrays (the results, the exact path,
the sketches) are `np.ndarray`. Array creation, `linalg.qr`, `conj`,
`reshape`, `moveaxis`, `concat` and `asarray` use the standard names.
Streams, pinned memory and CuPy's pool stay behind `utils._backend`, and
the Gaussian sketches are always drawn on the host.

Closes #45

Assisted-by: GitHub Copilot:Claude Opus 5.5
@github-actions

github-actions Bot commented Oct 15, 2026 •

Copy link
Copy Markdown
Contributor

Test Results

    5 files  ±0      5 suites  ±0   3m 36s ⏱️ +10s
  266 tests ±0    259 ✅ ±0   7 💤 ±0  0 ❌ ±0 
1 268 runs  ±0  1 253 ✅ ±0  15 💤 ±0  0 ❌ ±0 

Results for commit ad66b7a. ± Comparison against base commit 3099627.

♻️ This comment has been updated with latest results.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

Cross-cutting GPU dispatch and lazy staging changes warrant final human review despite the reported passing tests and benchmarks.

0 open findings

What changed in this PR

Adopts array API compatibility for SRC’s CPU/GPU dispatch and array typing, addressing issue #45.

Changes:

  • Infers namespaces from arrays and centralizes device selection in DeviceGroup.
  • Introduces backend-generic Array annotations and lazy staging-buffer allocation.
  • Updates dependencies, existing tests, and contributor documentation.
File Description
uv.lock Locks the compatibility dependency.
tests/​test_store.py Updates store construction.
tests/​test_stack.py Updates sweep calls.
tests/​test_sites.py Updates device-group setup.
tests/​test_plan.py Updates budget-resolution tests.
tests/​test_package.py Updates sketch calls.
tests/​test_group.py Updates group construction.
tests/​test_backend.py Checks namespace detection and updated copying.
src/​src_method/​utils/​linalg.py Infers QR backend; keeps SVD host-side.
src/​src_method/​utils/​_backend.py Provides compatibility namespaces and array alias.
src/​src_method/​utils/​__init__.py Exports Array.
src/​src_method/​stack.py Passes device selection to the sweep.
src/​src_method/​compress.py Clarifies host-array return typing.
src/​src_method/​apply.py Clarifies host-array return typing.
src/​src_method/​_tensor_train.py Distinguishes generic and host arrays.
src/​src_method/​_sweep.py Removes namespace parameters; uses standard operations.
src/​src_method/​_store.py Infers backend and allocates staging rings lazily.
src/​src_method/​_sites.py Gets its namespace from DeviceGroup.
src/​src_method/​_plan.py Resolves budgets through device groups.
src/​src_method/​_kernels.py Uses backend-generic array annotations.
src/​src_method/​_group.py Centralizes namespace and device selection.
pyproject.toml Adds array-api-compat at runtime.
docs/​content/​docs/​getting-started.mdx Updates runtime dependency guidance.
docs/​content/​docs/​contributing/​index.mdx Documents namespace and typing conventions.
docs/​content/​docs/​contributing/​dependencies.mdx Lists the new dependency.
AGENTS.md Updates backend-development guidance.

🧠 Review effort: Balanced


💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

@Panadestein
Panadestein merged commit a996df3 into main Oct 15, 2026
10 checks passed
@Panadestein
Panadestein deleted the refactor/array-api branch October 15, 2026 10:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Adopt the Python array API standard for backend dispatch and array typing

2 participants