Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
110 commits
Select commit Hold shift + click to select a range
ceec96f
perf: port the paper-aligned layer-build engine + operator sharding r…
diagonal-hamiltonian Jul 18, 2026
7b7bb3d
test(cpp): update C++ unit tests for the sharded layer-build engine
diagonal-hamiltonian Jul 18, 2026
ced7c3c
test(python): adapt white-box and upper_atol tests to the sharded engine
diagonal-hamiltonian Jul 18, 2026
d0d50ea
refactor(threading): ♻️ rewrite pool-dispatch sites as plain serial l…
diagonal-hamiltonian Jul 19, 2026
a40b71c
test(cpp): ♻️ drop the thread-pool dimension from the upper-atol resc…
diagonal-hamiltonian Jul 19, 2026
b454338
refactor(threading)!: 🔥 delete the in-repo thread pool; shards are th…
diagonal-hamiltonian Jul 19, 2026
d366cdb
refactor(profiling): 🔥 make the region profiler wall-clock only
diagonal-hamiltonian Jul 19, 2026
e9cf968
refactor(shard): ♻️ wall Linux topology/pinning behind the CpuTopolog…
diagonal-hamiltonian Jul 19, 2026
2ac06fa
chore: 🔥 drop stale oneTBB references
diagonal-hamiltonian Jul 19, 2026
dea2122
docs: 📝 document the shard-only parallelism model
diagonal-hamiltonian Jul 19, 2026
1210588
feat(benches): 📈 sweepable pauli chain topology + per-model stats
diagonal-hamiltonian Jul 19, 2026
b4cafad
fix(benches): 🐛 skip operator storage accounting on sharded propagators
diagonal-hamiltonian Jul 19, 2026
90af5da
feat(operator): 📊 operator memory/popcount diagnostics + split inline…
diagonal-hamiltonian Jul 19, 2026
31f94dd
merge: 🔀 bench pauli-chain harness + operator diagnostics into pauli-…
diagonal-hamiltonian Jul 19, 2026
e6afdd3
feat(pauli)!: ⚡ store PauliPropagator in the native local symplectic …
diagonal-hamiltonian Jul 19, 2026
22bd3ff
merge: 🔀 JW-free native-Pauli PauliPropagator into align
diagonal-hamiltonian Jul 19, 2026
e93b4b9
perf(evolution)!: 🔥 drop persistent fold-cache, always recompute cos
diagonal-hamiltonian Jul 20, 2026
769db75
Merge branch 'main' into perf/align-with-paper
matteoacrossi Jul 20, 2026
eca8e90
simplifications
diagonal-hamiltonian Jul 20, 2026
31c6c42
fixed benches
diagonal-hamiltonian Jul 20, 2026
e46a5f5
simp
diagonal-hamiltonian Jul 20, 2026
3260a38
perf(scan): ⚡ zero-postings fold early-out + lazy sparse-pivot expansion
diagonal-hamiltonian Jul 21, 2026
a192598
test(cpp): 🧹 extract shared test helpers, merge single-case files
diagonal-hamiltonian Jul 21, 2026
d587c9c
refactor(validation)!: 🔥 delete dead validators and EvolutionMode
diagonal-hamiltonian Jul 21, 2026
a369891
test(cpp): ✅ unit-test previously-uncovered data structures
diagonal-hamiltonian Jul 21, 2026
3c540dc
build,ci: 🔧 wire the wide-term-index (64-bit) build
diagonal-hamiltonian Jul 21, 2026
db8dcd2
docs(cpp): list the new unit-test files in the suite README
diagonal-hamiltonian Jul 21, 2026
cc2e08f
refactor(core)!: ♻️ recast the C++ core as a generic monomial/algebra…
diagonal-hamiltonian Jul 21, 2026
c8256c6
Merge branch 'main' into perf/align-with-paper
matteoacrossi Jul 21, 2026
bc03263
Merge branch 'main' into perf/align-with-paper
SimoneGasperini Jul 22, 2026
d1011b5
test(cpp): ✅ unit-test the graph-encoding packing/layout functions
diagonal-hamiltonian Jul 21, 2026
18a68e6
test(cpp): ✅ white-box MPGraph transform + MPOperator branch coverage
diagonal-hamiltonian Jul 21, 2026
12563c0
test(cpp): ✅ multi-rank Schrödinger fresh-insert resolve equivalence
diagonal-hamiltonian Jul 21, 2026
a9c353e
refactor: 🔥 delete verified-dead symbols (Tier 1 + Tier 2)
diagonal-hamiltonian Jul 21, 2026
30cc1af
lint
diagonal-hamiltonian Jul 22, 2026
9381d7f
fix(cmake): 🐛 recreate Threads imported target for downstream consumers
diagonal-hamiltonian Jul 22, 2026
88dcaa4
test(cpp): 🐛 make allreduce_sum_inplace check reduce identical stored…
diagonal-hamiltonian Jul 22, 2026
0390856
style: 🎨 formatter-clean branch files and add missing license headers
diagonal-hamiltonian Jul 22, 2026
8b8ae09
chore: 🔀 merge main into perf/align-with-paper
Copilot Jul 22, 2026
a0a5886
docs: 📝 update evolved_operator examples for the PauliOperator API
diagonal-hamiltonian Jul 22, 2026
e6c7e5e
refactor: ♻️ address safe SonarQube code smells
diagonal-hamiltonian Jul 22, 2026
a8dc05d
chore: 🔀 merge main into perf/align-with-paper
diagonal-hamiltonian Jul 22, 2026
016aab5
sharding tests
diagonal-hamiltonian Jul 22, 2026
1bb2002
fix macos sharding test
diagonal-hamiltonian Jul 22, 2026
ecba31c
annihilation: comments
diagonal-hamiltonian Jul 22, 2026
48f646b
Merge branch 'main' into perf/align-with-paper
ludmilaasb Jul 23, 2026
96fecaf
docs
diagonal-hamiltonian Jul 23, 2026
47fe393
refactor(operator): ♻️ simplify operator storage to the single-writer…
diagonal-hamiltonian Jul 23, 2026
8af2097
small fixes
diagonal-hamiltonian Jul 23, 2026
2671389
Merge branch 'refactor/simplify-operator-objects' into perf/align-wit…
diagonal-hamiltonian Jul 23, 2026
5fd5053
docs
diagonal-hamiltonian Jul 23, 2026
fe2ec4c
chore: update benchmark scripts and plots
Jul 24, 2026
acf4302
Merge branch 'perf/align-with-paper' of https://github.com/Algorithmi…
Jul 24, 2026
58f5ea9
chore: update benchmark documentation
Jul 24, 2026
8280fd1
chore: revert changes in benchmark script and documentation, moving t…
ludmilaasb Jul 24, 2026
0c6c3fb
chore: update the benchmark scripts and documentation
ludmilaasb Jul 24, 2026
8b2603b
docs
diagonal-hamiltonian Jul 24, 2026
1894f08
Merge branch 'perf/align-with-paper' into chore/update-benchmarks
ludmilaasb Jul 24, 2026
a043390
refact: major refactoring of script to more sensible memory accounting
Jul 24, 2026
9d3fe1f
refactor(evolution): ♻️ unify cross-rank resolve twins behind a sink …
diagonal-hamiltonian Jul 23, 2026
7f68a65
chore: 🔥 drop stale wide-TermIndex preset, threads comment, and bench…
diagonal-hamiltonian Jul 24, 2026
eb396d6
refactor(graph): ♻️ simplify the graph object, removing dead code and…
diagonal-hamiltonian Jul 24, 2026
8fa6e6a
refactor(evolution): ♻️ fold self-resolve + finalize into the sink po…
diagonal-hamiltonian Jul 24, 2026
45e1aff
simplifications
diagonal-hamiltonian Jul 24, 2026
129a867
refactor(graph): ♻️ harden the lazy derivative exchange layout
diagonal-hamiltonian Jul 24, 2026
ea64000
refact: updated how results.json is created
Jul 24, 2026
89010b6
chore: update benchmark figures
Jul 24, 2026
a867068
Merge branch 'perf/align-with-paper' into chore/update-benchmarks
ludmilaasb Jul 24, 2026
456b997
cleaning: remove some bits
diagonal-hamiltonian Jul 26, 2026
31c3704
feat(operator): 📈 add per-field operator memory diagnostics
diagonal-hamiltonian Jul 26, 2026
339c133
perf(operator): 🗜️ store the Heisenberg reference state sparsely
diagonal-hamiltonian Jul 26, 2026
aa0ddb6
docs(comments): 🔥 drop narration of superseded simulator versions
diagonal-hamiltonian Jul 26, 2026
7d10826
refactor(operator): 🔥 drop the delta-codec oracle diagnostics
diagonal-hamiltonian Jul 26, 2026
8571c35
merge: 🗜️ sparse Heisenberg reference state (L1)
diagonal-hamiltonian Jul 26, 2026
bc6865e
merge: origin/perf/align-with-paper
diagonal-hamiltonian Jul 26, 2026
d361d6b
fix(cmake): 🐛 actually enable MPI in the ctest matrix
diagonal-hamiltonian Jul 26, 2026
26a072d
fix(qiskit)!: 🐛 negate the generator across the qiskit boundary
diagonal-hamiltonian Jul 26, 2026
d54a7fa
fix(circuit): 🐛 close four front-end validation gaps
diagonal-hamiltonian Jul 26, 2026
75fc23b
fix(engine): 🐛 bounds-check indices and validate the cutoff config in…
diagonal-hamiltonian Jul 26, 2026
481b1ef
fix(evolution): 🐛 stop retaining a pointer into the mutable row-parit…
diagonal-hamiltonian Jul 26, 2026
d286d56
fix(mpi): 🐛 own the posted request, guard the count arithmetic, join …
diagonal-hamiltonian Jul 26, 2026
cc264c6
fix(mpi): 🐛 abort instead of hanging when a rank cannot reach a colle…
diagonal-hamiltonian Jul 26, 2026
043afb5
fix(graph): 🐛 report the real cosine-index count in graph_size
diagonal-hamiltonian Jul 26, 2026
927bfcc
refactor(algebra): ♻️ put the slot bound on the cutoff and de-duplica…
diagonal-hamiltonian Jul 26, 2026
b1c1cbf
test(mpi): 🐛 take serial_comm for the rank-local assertions
diagonal-hamiltonian Jul 26, 2026
2da5cb7
refactor(pare): ♻️ delete the no-op cross-rank keep-set exchange
diagonal-hamiltonian Jul 26, 2026
6c91972
refactor(notation)!: ♻️ name the initial state and monomials algebra-…
diagonal-hamiltonian Jul 27, 2026
4954507
perf(eval)!: 🗜️ materialize the dense reference state only for the gr…
diagonal-hamiltonian Jul 27, 2026
7ee4872
docs(comments): 📝 make comments state what the code cannot
diagonal-hamiltonian Jul 27, 2026
859ac37
style(cpp): 🎨 clang-format three files the last two commits left unfo…
diagonal-hamiltonian Jul 27, 2026
a4fa97c
Merge branch 'perf/align-with-paper' into chore/update-benchmarks
ludmilaasb Jul 27, 2026
724f365
Merge branch 'main' into chore/update-benchmarks
ludmilaasb Jul 28, 2026
4640053
fix: fixing merging errors
ludmilaasb Jul 28, 2026
d932f7b
lint: fix liting
ludmilaasb Jul 28, 2026
13d4550
Merge branch 'main' into chore/update-benchmarks
ludmilaasb Jul 29, 2026
fb16dee
Merge branch 'main' into chore/update-benchmarks
ludmilaasb Jul 29, 2026
463476a
Merge branch 'main' into chore/update-benchmarks
robertodr Jul 29, 2026
19aa477
Update benches/third_party/majorana_prop/monoprop_hubbard1d_benchmark.py
ludmilaasb Jul 30, 2026
c8cd105
Merge branch 'main' into chore/update-benchmarks
robertodr Jul 31, 2026
7948a02
Merge remote-tracking branch 'origin/main' into chore/update-benchmarks
Panadestein Aug 10, 2026
6a3d1e4
chore: linting.
Panadestein Aug 10, 2026
c50699e
feat: update memory high water mark measurement
Panadestein Aug 10, 2026
4124760
Merge branch 'main' into chore/update-benchmarks
Panadestein Aug 10, 2026
380b602
fix(benches): :bug: measure every Pauli backend's memory the same way
Panadestein Aug 10, 2026
1f08f57
feat: add GPU memory measurement primitives and update benchmarks to …
Panadestein Aug 11, 2026
afa683c
Merge branch 'main' into chore/update-benchmarks
Panadestein Aug 11, 2026
19591d3
Merge branch 'main' into chore/update-benchmarks
Panadestein Aug 11, 2026
fd9b0ac
feat: enhance GPU memory tracking with MemoryHook and update document…
Panadestein Aug 11, 2026
f63a602
fix: correct plotting script.
Panadestein Aug 11, 2026
7cd220b
fix: final touches to the plotting script.
Panadestein Aug 12, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions benches/_memory.py → benches/_memory_cpu.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,11 @@ def peak_rss_bytes() -> int:
def reset_peak_rss() -> bool:
"""Reset ``VmHWM`` to the current RSS, starting a new measurement window.

This also resets ``getrusage(...).ru_maxrss`` (and so Julia's ``Sys.maxrss()``): both
report the same kernel field, ``mm->hiwater_rss``. Any process that opens a window
therefore loses ``ru_maxrss`` as a whole-run ceiling, and must take the maximum over
its windows instead.

Returns:
``True`` if the reset took effect, ``False`` where ``/proc/self/clear_refs`` is
unavailable (non-Linux, kernel < 4.0, or a restricted sandbox), in which case
Expand Down
324 changes: 324 additions & 0 deletions benches/_memory_gpu.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,324 @@
# Copyright 2026 Algorithmiq
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""Device-memory measurement, the GPU counterpart of ``benches/_memory_cpu.py``.

The host metric works because the kernel maintains ``VmHWM`` on every RSS increase: a
peak, not a sample, so no transient can hide between polls. This module looks for the
same guarantee on the device, and reports which one it found rather than pretending the
answer is uniform.

Which strategy applies depends on the allocator CuPy is using, so
:class:`DeviceHighWaterMark` records it in :attr:`~DeviceHighWaterMark.method`:

``async-pool``
CuPy on ``malloc_async``. CUDA's stream-ordered pool keeps
``cudaMemPoolAttrUsedMemHigh``, a true high-water mark of bytes in use, and writing 0
to it resets it to the current value -- a resettable window, exactly like
``clear_refs`` on the host. Exact, and it needs no Python-side bookkeeping at all.

``hook``
CuPy's default (caching) allocator, tracked via a :class:`cupy.cuda.MemoryHook`.
The hook's callbacks fire synchronously, in-process, on every malloc/free CuPy makes
-- not on a timer, so nothing can happen between polls the way it could with a
background sampler. Each callback takes ``max(peak, pool.used_bytes())``, which is
exact for the same reason ``VmHWM`` is: it is updated at the moment of the event, not
read back later.

``unavailable``
No CuPy, or the hook API is missing (very old CuPy) and the async pool is also absent.
Everything reads zero.
"""

from __future__ import annotations

from typing import TYPE_CHECKING, Any

if TYPE_CHECKING:
from types import TracebackType
from typing import Self

# `method` values; see the module docstring.
ASYNC_POOL = "async-pool"
HOOK = "hook"
UNAVAILABLE = "unavailable"

# How each strategy's number should be described wherever it is published.
DEVICE_MEMORY_METRICS = {
ASYNC_POOL: "peak GPU bytes in use over the step (cudaMemPoolAttrUsedMemHigh)",
HOOK: "peak GPU bytes in use over the step (CuPy MemoryHook on the default pool)",
UNAVAILABLE: "no GPU memory reading available",
}


def _async_pool() -> int | None:
"""Return the device's default CUDA memory pool, or None if it is not in use.

The pool exists on any CUDA >= 11.2 device, but it only *observes* the allocations
under test when CuPy routes them through it, so an allocator check comes first --
otherwise the counters read a pool nobody is allocating from and report zero.
"""
try:
import cupy as cp
from cupy.cuda import runtime
except ImportError:
return None
try:
if cp.cuda.get_allocator() is not cp.cuda.malloc_async:
return None
return runtime.deviceGetDefaultMemPool(runtime.getDevice())
except (AttributeError, RuntimeError): # old CuPy, or no device
return None


def _pool_attr(pool: int, attr_name: str) -> int:
"""Read one ``cudaMemPoolAttr`` counter, or 0 if unavailable."""
try:
from cupy.cuda import runtime

return int(runtime.memPoolGetAttribute(pool, getattr(runtime, attr_name)))
except (AttributeError, RuntimeError):
return 0


def reset_peak_device_bytes(pool: int) -> bool:
"""Reset the pool's used-memory high-water mark to its current occupancy.

CUDA defines writing 0 to ``cudaMemPoolAttrUsedMemHigh`` as "set it to
``cudaMemPoolAttrUsedMemCurrent``", which is what opens a fresh window.
"""
try:
from cupy.cuda import runtime

runtime.memPoolSetAttribute(pool, runtime.cudaMemPoolAttrUsedMemHigh, 0)
except (AttributeError, RuntimeError): # pragma: no cover - driver dependent
return False
return True


def device_synchronize() -> None:
"""Block until the device is idle, so a reading covers completed work.

Launches are asynchronous: without this, a counter can be read before the allocations
it is meant to cover have happened.
"""
try:
import cupy as cp

cp.cuda.Device().synchronize()
except (ImportError, AttributeError, RuntimeError):
pass


def _caching_pool_used_bytes() -> int:
"""Return bytes live in CuPy's default pool right now, 0 if unavailable."""
try:
import cupy as cp

return int(cp.get_default_memory_pool().used_bytes())
except (ImportError, AttributeError, RuntimeError):
return 0


_hook_class: type[Any] | None = None


def _peak_hook_class() -> type[Any] | None:
"""Build (once) and return a ``MemoryHook`` subclass that tracks a running peak.

Built lazily and cached at module level: the base class only exists once CuPy is
importable, so it cannot be defined at import time. The callbacks accept ``**kwargs``
rather than CuPy's documented parameter names, so a signature change in some CuPy
version does not silently stop the tracking -- it would raise loudly instead of the
hook simply never firing.
"""
if _peak_hook_class.cached is not None:
return _peak_hook_class.cached
try:
from cupy.cuda import memory_hook
except ImportError:
return None

class _PeakUsedBytesHook(memory_hook.MemoryHook):
name = "monoprop_peak_used_bytes_hook"

def __init__(self, pool: Any) -> None:
self._pool = pool
self.peak_bytes = int(pool.used_bytes())

def _update(self, **_kwargs: object) -> None:
self.peak_bytes = max(self.peak_bytes, int(self._pool.used_bytes()))

def malloc_postprocess(self, **kwargs: object) -> None:
self._update(**kwargs)

def free_postprocess(self, **kwargs: object) -> None:
self._update(**kwargs)

_peak_hook_class.cached = _PeakUsedBytesHook
return _PeakUsedBytesHook


_peak_hook_class.cached = None


class DeviceHighWaterMark:
"""Peak device memory over the enclosed block.

Mirrors :class:`_memory_cpu.HighWaterMark`: synchronizes on entry and exit so the reading
covers completed work, and exposes the same ``peak``/``baseline``/``delta`` vocabulary.

``exact`` is ``True`` under both the ``async-pool`` and ``hook`` strategies: the first
reads a driver-maintained high-water counter, the second updates its own peak
synchronously on every allocation event CuPy makes, so neither can miss a transient
that lived and died inside the block. Only ``unavailable`` degrades the figure (to
zero); :attr:`method` says which applies -- check it before publishing.
"""

def __init__(self) -> None:
self._pool = _async_pool()
self._hook: Any = None
self.method = UNAVAILABLE
self.baseline_bytes = 0
self.peak_bytes = 0
self.exact = False

def __enter__(self) -> Self:
device_synchronize()
if self._pool is not None and reset_peak_device_bytes(self._pool):
self.exact = True
self.method = ASYNC_POOL
self.baseline_bytes = _pool_attr(
self._pool, "cudaMemPoolAttrUsedMemCurrent"
)
self.peak_bytes = self.baseline_bytes
return self

# Not on the async pool (or too old a CuPy to reset it): fall back to tracking
# the default pool's `used_bytes()` synchronously, via a hook, rather than the
# driver-native counter.
self._pool = None
hook_cls = _peak_hook_class()
if hook_cls is None:
self.baseline_bytes = _caching_pool_used_bytes()
self.peak_bytes = self.baseline_bytes
return self

try:
import cupy as cp

pool = cp.get_default_memory_pool()
self._hook = hook_cls(pool)
self._hook.__enter__()
except (ImportError, AttributeError, RuntimeError):
self._hook = None
self.baseline_bytes = _caching_pool_used_bytes()
self.peak_bytes = self.baseline_bytes
return self

self.exact = True
self.method = HOOK
self.baseline_bytes = self._hook.peak_bytes
self.peak_bytes = self.baseline_bytes
return self

def __exit__(
self,
exc_type: type[BaseException] | None,
exc: BaseException | None,
tb: TracebackType | None,
) -> None:
device_synchronize()
if self.method == ASYNC_POOL and self._pool is not None:
observed = _pool_attr(self._pool, "cudaMemPoolAttrUsedMemHigh")
elif self.method == HOOK and self._hook is not None:
self._hook.__exit__(None, None, None)
observed = self._hook.peak_bytes
else:
# Nothing tracked the block: this is the same single end-of-block sample that
# proved unreliable for the caching pool. Report it, but the method stays
# `unavailable` so callers do not mistake it for an exact reading.
observed = _caching_pool_used_bytes()
self.peak_bytes = max(self.baseline_bytes, observed)

@property
def metric(self) -> str:
"""Return the description of what :attr:`peak_bytes` counts."""
return DEVICE_MEMORY_METRICS[self.method]

@property
def delta_bytes(self) -> int:
"""Return the peak measured above the block's own starting floor."""
return self.peak_bytes - self.baseline_bytes

@property
def peak_mb(self) -> float:
"""Return :attr:`peak_bytes` in MiB."""
return self.peak_bytes / 1024**2

@property
def baseline_mb(self) -> float:
"""Return :attr:`baseline_bytes` in MiB."""
return self.baseline_bytes / 1024**2

@property
def delta_mb(self) -> float:
"""Return :attr:`delta_bytes` in MiB."""
return self.delta_bytes / 1024**2


def _self_check() -> int:
"""Allocate a transient that only a high-water mark can see, and report.

The transient is freed before the window closes, so an end-of-step occupancy reading
misses it entirely -- that gap is the bug this module exists to close. Run this on the
GPU host to find out which strategy is active and whether it catches the transient.
"""
try:
import cupy as cp
except ImportError:
print("cupy is not installed: nothing to check")
return 1

transient_mb, resident_mb = 512, 8
keep = None
with DeviceHighWaterMark() as window:
blob = cp.zeros(transient_mb * 1024**2 // 8, dtype=cp.float64)
blob += 1 # touch it, so the allocation is real
del blob
keep = cp.zeros(resident_mb * 1024**2 // 8, dtype=cp.float64)
end_of_step_mb = _caching_pool_used_bytes() / 1024**2
del keep

print(f"strategy : {window.method} (exact={window.exact})")
print(f"metric : {window.metric}")
print(f"baseline : {window.baseline_mb:8.1f} MB")
print(f"peak : {window.peak_mb:8.1f} MB")
print(f"delta : {window.delta_mb:8.1f} MB (expected >= {transient_mb})")
print(
f"end-of-step : {end_of_step_mb:8.1f} MB (the old metric; misses the peak)"
)

if window.method == UNAVAILABLE:
print("\nFAIL: no device reading available")
return 1
if window.delta_mb < transient_mb * 0.9:
print(f"\nFAIL: the {transient_mb} MB transient was not captured")
return 1
print("\nPASS: the freed transient is visible in the peak")
return 0


if __name__ == "__main__":
raise SystemExit(_self_check())
2 changes: 1 addition & 1 deletion benches/bench_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@

import pytest
from _builders import MODELS, barriered
from _memory import resting_rss_bytes
from _memory_cpu import resting_rss_bytes


@pytest.mark.slow
Expand Down
2 changes: 1 addition & 1 deletion benches/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@
build_random_propagator,
make_random_problem,
)
from _memory import (
from _memory_cpu import (
HighWaterMark,
PssSampler,
merge_peak_of_sum,
Expand Down
21 changes: 17 additions & 4 deletions benches/third_party/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,10 +70,23 @@ instead of squares with `--ny`.

Two things to know when reading the output:

- **The memory column is not one quantity.** Each backend reports what it exposes —
monoprop and cuPauliProp their own operator footprint, `PauliPropagation.jl`
`Base.summarysize`, and ppvm and Qiskit only a process-RSS proxy. `MEMORY_METRICS` in
`backends.py` records which is which, and every record also carries `peak_rss_MB`.
- **The memory column is one quantity for every backend**: the peak resident set size over
each step, read from the kernel's `VmHWM` high-water mark and reset per step, so the
curves may be compared directly. Where a library also accounts for itself, that figure is
kept separately as `operator_memory_MB` / the `native_memory` series — those are *not*
commensurable across backends (one counts an operator, another an object graph, another
device memory), so never plot them against each other. `HOST_MEMORY_METRIC` and
`OPERATOR_MEMORY_METRICS` in `backends.py` record which is which, and every record also
carries `peak_rss_MB` for the process lifetime.
- **The GPU backend's own figure is a device high-water mark, not an end-of-step reading**,
so a transient freed inside a step still counts. `benches/_memory_gpu.py` picks the strongest
counter the allocator allows and names it in `operator_memory_metric`: CUDA's resettable
`cudaMemPoolAttrUsedMemHigh` when CuPy runs on `malloc_async`, otherwise a `MemoryHook`
that tracks the default pool's `used_bytes()` synchronously on every allocation event —
both are exact. (An earlier version read `total_bytes()` once at the end of the block;
that missed transients because CuPy can return freed blocks to the driver before the
block closes, so it was replaced.) Run `python ../_memory_gpu.py` on the GPU host to see
which strategy is active and confirm it catches a freed transient.
- **`PauliPropagation.jl` runs in its fastest documented configuration**, which is not its
default: the `VectorPauliSum` container driven by `Performance.propagate!`, with
coefficient truncation on. That combination needs the **dev branch (0.8.0)** — earlier
Expand Down
Loading
Loading