Skip to content

Bounded memory for long inputs: streaming front end (1.1.0) - #14

Merged
danigb merged 23 commits into
mainfrom
feat/bounded-memory
Oct 2, 2026
Merged

danigb merged 23 commits into
mainfrom
feat/bounded-memory

Conversation

@danigb

@danigb danigb commented Oct 2, 2026 •

Copy link
Copy Markdown
Owner

Why

⚠️ Made with Claude

BeatThis::analyze_audio held the whole signal several times over before the (already chunked) beat model ran, so peak memory grew by about 5.3 GiB per hour of audio. This PR makes the front end stream.

What changes

Added

  • BeatThis::stream and BeatStream::{push, finish, finish_timed}: push mono f32 chunks of any size as they are decoded; the whole signal never has to be in memory.
  • BeatThis::analyze_owned / analyze_owned_timed: take the caller's Vec<f32> and free it before the beat model runs.

Changed

  • analyze_audio, analyze_owned and analyze_file run through BeatStream.
  • The rten backend no longer clones model inputs or outputs.
  • The mel spectrogram is computed in 64-frame-aligned windows of 1,536 frames instead of one whole-signal graph run.
  • The resampler runs in 8,192-frame chunks at every input rate.
  • A zero sample rate returns an error (1.0.0 panicked for inputs of 3 or more samples).

The public API is additive only; no 1.0.0 signature changes.

Memory

Peak memory footprint in MiB, median of three, Apple M4 Pro (macOS), full model, synthetic mono input at 48 kHz:

minutes stream analyze_audio (this PR)
5 533.7 590.3
10 542.2 651.0
20 558.0 779.0
60 620.5 1,278.5
  • stream grows linearly by 93.8 MiB per hour of audio.
  • 1.0.0's analyze_audio measured 5,651.0 MiB at 60 minutes. That baseline cell was noisy on this host.

Output compared with 1.0.0

Identity is checked by tests/identity_v1_0_0.rs, which runs a verbatim copy of the 1.0.0 pipeline live and compares bits.

  • stream vs analyze_audio: bit-identical at every rate tested and for every push size.
  • 22,050 Hz input and 22,050·2^k Hz sources (11.025, 44.1, 88.2 kHz): bit-identical to 1.0.0.
  • Other rates (48 kHz etc.), short inputs: a small drift in the resampled PCM (max |Δ| 1.86e-4 at 20 minutes). Beats and downbeats were bit-identical on every input tested: a 23-file corpus decoded at 48 kHz with the full model, and synthetic input up to 50 minutes.
  • Other rates, long inputs: beats can move by one frame. On 60 minutes of synthetic 48 kHz input, 2 of 7,200 beats and 2 of 5,886 downbeats moved 20 ms earlier.

The long-input difference comes from 1.0.0, not from this PR. 1.0.0's whole-input resampler call accumulates position error, which grows sharply past 2^27 input frames (46.6 minutes at 48 kHz). Against an analytic 1 kHz tone over 60 minutes (resampler_accuracy_long), the chunked resampler's per-minute RMS error stays at 3.2e-6 to 3.3e-6, while 1.0.0's grows to 1.14e-2.

load_audio to target rates other than 22,050 Hz also differs from 1.0.0 where the source/target ratio is not exact; the CHANGELOG has the numbers.

What was and was not tested

  • arm64 (NEON): everything, including the ignored long tests with the full model.
  • x86_64 AVX2+FMA, under QEMU emulation: the identity binary up to the windowed-mel routing commit (cddc70b) only. That pass was a rerun after a QEMU segfault that also occurs with the older binary.
  • Not run on x86: the drift tests and all stream tests. This PR's CI is their first native x86 run.
  • AVX-512: never executed; the mel window alignment for it is derived from the rten-gemm source.
  • Linux memory: not measured.
  • Dependency ranges: unchanged. Identity is verified against this repo's Cargo.lock; a consumer resolving a newer rten or rubato patch release could lose bit identity.

Notes for review

  • cargo test is heavier than before: the identity binary takes about 85 s wall on 14 cores. Worth checking the CI duration against the 20-minute timeout.
  • #[doc(hidden)] pub mod __probe is unstable test and measurement infrastructure, used by examples/memory_probe.rs and the identity tests.
  • The last commit, "Bump version to 1.1.0", is separate so it can be dropped if the bump belongs to the release step.

danigb added 22 commits October 2, 2026 10:06
@danigb
danigb force-pushed the feat/bounded-memory branch from 9a1043f to b83f457 Compare October 2, 2026 16:41
Resolve the audio.rs conflict by keeping #13's `decode` submodule and 1.1.0's
early `drop(samples)` in `load_audio`. Gate the 1.1.0-only tests
(identity_v1_0_0, stream_integration) and the crate-level doc example on
`decode`, add a `--no-default-features` clippy/test step to CI, note the new
features in the CHANGELOG and date the 1.1.0 entry.
@danigb
danigb merged commit 1ae768e into main Oct 2, 2026
2 checks passed
@danigb
danigb deleted the feat/bounded-memory branch October 2, 2026 17:17
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.

1 participant