AudioOperation execution dependencies¶
Wandas separates storage chunking from numerical-kernel execution. Frame data is normally chunked with one channel per Dask chunk, but that topology alone does not make a delayed NumPy/SciPy kernel run once per channel. The operation's internal graph-building specialization owns that decision.
Channel dependency and time dependency are independent dimensions. An operation can be channel-independent while still requiring one complete, continuous time series for each channel. Channel-wise execution therefore reduces the number of channels that a kernel task materializes; it does not introduce time-axis distribution.
Internal graph-builder contract¶
AudioOperation.process() validates inputs and calculates output shape and dtype,
then delegates graph construction through one polymorphic internal hook:
- The base implementation is conservative whole-frame execution. One delayed kernel receives the complete channel-first tensor, preserving all existing operations and custom extensions.
- The channel-independent specialization builds one delayed kernel call for each
channel, where the kernel input retains shape
(1, ...), then concatenates the outputs along the channel axis.
The prototype contract is deliberately narrow: channel-wise execution currently
applies to unary operations with a known positive channel count that preserve the
channel axis. Zero-channel, unknown-channel-count, multi-input, and channel-axis-changing
inputs fall back to the base whole-frame graph. This makes channel-wise execution an
opportunistic optimization without strengthening the AudioOperation input contract.
New execution forms override the graph-building hook instead of adding cases to a
central dispatcher. The hook, Dask graph, chunks, scheduler, and xarray container
remain private implementation details; the public Frame workflow is unchanged.
Built-in operation classification¶
The table classifies the registered numerical families. “Whole signal/axis” means the operation needs the complete relevant axis for one channel; it does not mean that all channels depend on one another.
| Built-in family | Channel dependency | Time or analysis-axis dependency | Current execution |
|---|---|---|---|
remove_dc |
Independent | Whole time series per channel for the mean | Channel-wise prototype |
abs, power |
Independent | Pointwise/time-local | Existing Dask-native graph override |
normalize |
Parameter-dependent: axis=-1 is independent; axis=None or a channel axis is cross-channel |
Whole selected norm axis | Whole-frame |
trim, fix_length |
Independent | Indexed/padded time-local transform with output-shape change | Whole-frame |
fade |
Independent | Needs the full signal length to define the envelope | Whole-frame |
| high-pass, low-pass, band-pass, A-weighting | Independent | Stateful/whole continuous time series per channel | Whole-frame |
| resampling | Independent | Stateful/whole continuous time series per channel | Whole-frame |
| RMS trend, sound level | Independent | Window/overlap-sensitive; weighting can add filter state | Whole-frame |
| FFT, IFFT, cepstrum, lifter, spectral envelope, N-octave analysis/synthesis | Independent | Whole transform axis per channel | Whole-frame |
| STFT, ISTFT, Welch, spectrogram cepstrum, HPSS | Independent | Window/overlap-sensitive or full analysis-axis context | Whole-frame |
| loudness, roughness, sharpness | Independent | Standard algorithms require complete or overlapping per-channel context | Whole-frame |
add_with_snr |
Corresponding channels from two inputs | Whole time series for RMS scaling | Whole-frame; multi-input is outside the prototype |
sum, mean, channel_difference |
Cross-channel | Pointwise after combining channels | Existing cross-channel Dask graph |
| coherence, CSD, transfer function | Cross-channel | Window/overlap-sensitive cross-spectral analysis | Whole-frame |
custom |
Unknown by construction | User-defined | Whole-frame |
This classification does not authorize time chunking for filters, resampling, FFT, STFT, Welch, psychoacoustic algorithms, or other continuity-sensitive transforms. Those operations remain whole-signal per channel until they have an explicit state or overlap contract.
Prototype: RemoveDC¶
RemoveDC is unary, shape-preserving, and numerically independent across channels. It
is the first operation to use the channel-independent specialization. For known,
positive channel counts, each task still receives the complete time series for one
channel, so subtracting that channel's mean is identical to the previous whole-frame
kernel call. Indeterminate or inapplicable inputs retain the historical whole-frame
behavior.
The Frame boundary is unchanged: calibration factors are applied lazily before the operation, consumed exactly once in output channel metadata, and channel IDs, labels, units, references, extra metadata, source-time offsets, semantic lineage, and Recipe replay continue through the existing construction path. Shape and dtype are calculated before execution as before.
Unsupported and cross-channel operations retain the default graph builder. This
fail-safe default is also used by third-party AudioOperation subclasses, so the
prototype does not silently reinterpret existing kernels as channel-independent.
Benchmark interpretation¶
The scalability benchmark accepts multiple values for --channels and runs both the
forced whole-frame baseline and the channel-wise prototype in isolated workers. It
computes the same remove_dc kernel and reports numerical evidence, graph task count,
operation compute time, absolute process peak RSS observed immediately after operation
execution, and the existing WDF metrics. Recipe extraction happens only after the
operation graph and operation-lifetime RSS measurements; recipe_nodes is therefore a
separate structural probe rather than part of the operation timing or allocation
window.
For a fixed sample count, compare rows with the same channel count and different
execution_path values, then compare increasing channel counts within each path. More
channel tasks and graph-building allocation are the expected tradeoff for avoiding one
kernel task that receives every channel. Timings and absolute RSS are meaningful only
for reruns on the same machine, Python environment, and dependency lock; the benchmark
does not define a platform-independent RSS ceiling.
Prototype benchmark evidence¶
The issue-328 comparison used one fixed 480,000-sample signal per channel and increased
the channel count through 1, 2, 4, and 8. The base revision was run through the same
candidate benchmark harness as a bridge, selecting only whole-frame; the candidate
ran both paths. The complete candidate matrix was repeated once because timing and RSS
are environment-sensitive.
| Channels | Tasks, whole → channel | Operation peak RSS MB, whole → channel | Rerun RSS MB, whole → channel |
|---|---|---|---|
| 1 | 6 → 6 | 158.1 → 158.0 | 157.1 → 157.9 |
| 2 | 12 → 14 | 172.6 → 165.6 | 173.1 → 165.4 |
| 4 | 20 → 28 | 195.6 → 181.3 | 195.7 → 181.2 |
| 8 | 36 → 56 | 241.2 → 230.0 | 242.7 → 227.8 |
Every paired output_l2_squared value is exactly equal. At eight channels, the two
candidate runs reduced the observed operation-lifetime peak by about 11.2–15.0 MB
while adding 20 graph tasks. At four channels, both runs reduced it by about 14.2–14.4
MB. These values characterize the observed tradeoff rather than define a portable
memory budget. Channel-wise compute time was lower for 4 and 8 channels in both runs;
the two-channel timing changed direction, so timing remains descriptive and has no
pass/fail threshold.
Environment and revisions:
- base:
e5c7c4f8a47e60fb79eef996d9595260579ea6c3; - candidate:
71ea46cabd747666b10a44991735a3c566caa7a3; - Linux
6.17.0-40-genericx86-64, glibc 2.36; - CPython 3.10.20, Clang 22.1.3;
- one shared virtual environment and lock file,
uv.lockSHA-2568f22e9d43bb9a4f1ec476219fb57464bd29929f8e7e30bc0d03c32f728414107.
Commands (absolute checkout prefixes are shown because the bridge run intentionally loaded the base library while executing the candidate harness):
PYTHONPATH=/workspaces/wandas /workspaces/wandas/.venv/bin/python \
/workspaces/wandas/.worktrees/issue-328/scripts/scalability_benchmark.py \
--channels 1 2 4 8 --samples 480000 --chunk-samples 480000 \
--sampling-rate 48000 --execution-paths whole-frame
PYTHONPATH=/workspaces/wandas/.worktrees/issue-328 \
/workspaces/wandas/.venv/bin/python scripts/scalability_benchmark.py \
--channels 1 2 4 8 --samples 480000 --chunk-samples 480000 \
--sampling-rate 48000
The second command was run twice without concurrent benchmark activity. Material RSS or timing differences must be rerun in the same environment and lock; cross-platform absolute RSS comparisons are invalid. The committed raw reports are intentional evidence artifacts: