Skip to content

Add opt-in native fp16 and regional torch.compile, optimize RoFormer/MPS inference, and move to a validated PyTorch 2.13 baseline - #298

Open
ntamotsu wants to merge 20 commits into
nomadkaraoke:mainfrom
ntamotsu:optimize-pytorch-stem-separation
Open

Add opt-in native fp16 and regional torch.compile, optimize RoFormer/MPS inference, and move to a validated PyTorch 2.13 baseline#298
ntamotsu wants to merge 20 commits into
nomadkaraoke:mainfrom
ntamotsu:optimize-pytorch-stem-separation

Conversation

@ntamotsu

@ntamotsu ntamotsu commented Aug 2, 2026

Copy link
Copy Markdown
Contributor

Interactive walkthrough: https://claude.ai/code/artifact/af4c08ee-e129-424f-a84e-43101b1b9e58

The same content as this description, as a page you can drive: a resolver that shows what any device/model/precision/compile request actually activates, a chunk schedule you can replay at any input length, a spill calculator for the MPS buffer budget, and the benchmark tables as charts.


Summary

This PR makes PyTorch stem separation faster and more robust while preserving default outputs. It adds two opt-in, independent execution controls — precision (--use_autocast / new --use_native_fp16) and regional compilation (new --use_torch_compile) — fixes several RoFormer correctness issues (rotary precision, tail/short-input chunk scheduling, linear-attention layouts), makes model loading reusable, and moves the validated baseline to PyTorch 2.13. Every unsupported combination logs a warning and safely continues with today's float32/eager behavior.

Headline results, measured against v0.44.5 on the exact same 99.000 s stereo 44.1 kHz float32 WAV (4,365,900 frames) for every MPS and CUDA timing cell, with each side built from its own lock (so the numbers reflect the combined effect of this PR — code changes plus the Torch 2.8 → 2.13 dependency move — not a code-only attribution):

  • MPS (Apple M4 Pro), warm eager: all five tested released models are faster than v0.44.5 — 3.26–18.63 % (fp32) and 14.41–25.65 % (autocast).
  • CUDA (Google Colab Tesla T4), warm eager: 0.24–10.05 % faster than v0.44.5 at fp32; with autocast, the three RoFormers and VR are 4.25–8.01 % faster, while HTDemucs autocast is 2.63 % slower.
  • Regional torch.compile (opt-in) on the released RoFormers cuts warm time further at equal precision: up to 33.93 % on MPS (fp32) and up to 43.18 % on CUDA (native fp16).
  • Native fp16 (opt-in) roughly halves retained RoFormer model tensors and post-run MPS allocation (it does not reduce whole-process peak RSS — details below).

What changes for users

Independent precision and compilation axes

  • --use_autocast and the new --use_native_fp16 are mutually exclusive precision modes (enforced in the CLI and the Separator constructor). The new --use_torch_compile is orthogonal and combines with any supported precision — autocast + compile is a valid pair.
  • Native fp16 converts verified models to float16 weights while keeping numerically sensitive work in float32: rotary angle construction, RMSNorm / l2norm, STFT/ISTFT, and the complex mask product.
  • Those float32 guards are not native-fp16-specific — they apply under autocast too. RMSNorm / l2norm upcast whenever the incoming tensor is fp16 or bf16, and the mask is cast back to the spectrum's dtype before the complex product, so both fire under autocast exactly as they do under native fp16 (under fp32 they are no-ops). The rotary guard is more explicit still: autocast_disabled(device) suppresses autocast on any backend, and fixing degraded CPU/MPS autocast angle precision is its whole purpose. STFT/ISTFT need no guard at all — they sit outside the low-precision region, before the cast into band_split and after the mask is cast back.
  • Regional compilation compiles only the repeated RoFormer transformer blocks (not the whole model), so STFT/scatter stay eager and one compiled graph is shared across blocks.

Verified combinations are intentionally conservative:

Device Model family Precision Regional torch.compile
MPS / CUDA MelBand RoFormer, BS-RoFormer fp32, autocast, native_fp16 supported with all three
CPU MelBand RoFormer, BS-RoFormer fp32, autocast supported with both
MPS / CUDA / CPU VR, Demucs, other PyTorch models fp32, autocast (as today) not yet verified → warns, stays eager
DirectML PyTorch models fp32 (as today) excluded → warns, stays fp32/eager
any ONNX models (MDX) managed by the ONNX Runtime provider n/a

Observable fallbacks

  • After load_model(), the read-only properties Separator.effective_precision ("fp32" | "autocast" | "native_fp16") and Separator.effective_torch_compile report what was actually activated, so warning-based fallbacks are visible to callers.
  • Regional compilation requires PyTorch ≥ 2.6; older supported Torch keeps the selected precision, warns, and stays eager. If a compiled block fails lazily during inference, the affected chunk is retried once eagerly, the eager module calls are restored, and effective_torch_compile reports False.

Model reuse and lifecycle (deliberate, documented behavior change)

  • load_model() now reuses the loaded instance when the same single model is requested again. Loading copies Separator configuration into the architecture instance — output directory and format, normalization settings, architecture parameters, and the requested precision/compile settings — so load_model(..., force_reload=True) exists for the one case where a caller mutates such configuration after the first load and wants the same model rebuilt with the new values. Ordinary fixed-configuration use never needs it. A failed (re)load keeps the previously working model and its metadata intact.
  • VR retains its loaded module across separations instead of reconstructing and re-reading weights per call.
  • Demucs intentionally keeps its existing per-separate() internal load/release lifecycle (its lightweight wrapper is reusable, memory behavior unchanged), now with exception-safe cleanup.
  • Multi-model ensembles keep their existing loading behavior. The README documents the memory semantics of the retained instance.

Correctness and robustness fixes

  • RoFormer chunk schedulerv0.44.5 re-anchors an overrunning chunk to mix[:, -chunk_size:] and writes it at result.shape[-1] - chunk_size. When the last two start positions on the step grid both overrun the end of the input, that produces two forwards over the byte-identical slice, written to the identical offset. The overlap-add is a weighted average (result / counter), so the duplicate adds the same Hamming window to counter twice and the tail chunk ends up double-weighted. On the 99 s input (chunk 485,100 samples = 11.000 s, step 352,800 = 8.000 s) forwards per run drop 13 → 12 with unchanged coverage, and across the 3.0 s the tail chunk shares with its predecessor (88.0–91.0 s) its weight goes from 2:1 back to the intended 1:1 — at the midpoint of that region, from 66.67 % to 50.00 % of the blend.

    This changes the output versus v0.44.5 in that overlap, deliberately: v0.44.5's weighting was the bug. Outside the overlap the tail chunk is the only contributor, so 2wy/2w = wy/w and the samples are identical. The numerical-parity section below is a within-branch comparison across execution modes and does not cover v0.44.5-vs-PR output equality, so a reviewer diffing against v0.44.5 should expect a difference confined to that tail window.

    The saving is input-length dependent, not universal. The duplicate only appears when two grid positions overrun the end, which happens for chunk/step - 1 = 37.5 % of input lengths at these settings. At 98 s it is 13 → 12 like 99 s; at 99.5 s, 100 s and 110 s both revisions produce identical schedules, identical forward counts, and identical output. The 99 s benchmark input happens to fall on the saving side, so every RoFormer timing cell in this campaign includes it.

    Inputs shorter than one chunk now work: L - chunk_size goes negative on v0.44.5 while length is forced to chunk_size, so a 5 s input crashes with The size of tensor a (176400) must match the size of tensor b (220500). The same crash reproduces with v0.44.5's code on Torch 2.13, so it is a code bug, not a Torch difference; this PR clamps the tail start to 0 and returns the full 220,500 frames from a single forward. The automatic short-audio segment override also no longer mutates persistent separator state.

  • Rotary embeddings stay float32 on every backendrotary-embedding-torch 0.6.x disables autocast only for CUDA (still true in 0.9.1; tracked in Avoid hard-coding autocast device parameter in rotary_embedding_torch.py lucidrains/rotary-embedding-torch#46), so CPU/MPS autocast could degrade angle precision. Rotation now runs inside a device-generic autocast-disabled float32 region, replaces any low-precision cached angles with float32, and skips cache mutation while Dynamo traces (avoiding per-instance recompilations).

  • Linear-attention BS-RoFormer layouts now load — configs with linear_transformer_depth > 0 previously failed to construct (Attend.__init__() got an unexpected keyword argument 'scale'). Attend now honors scale on both the SDPA and einsum paths, and the loader/normalizer forward linear_transformer_depth.

  • SDPA context migrated from the deprecated torch.backends.cuda.sdp_kernel to torch.nn.attention.sdpa_kernel with the same effective backend set; this is also what lets Dynamo trace attention without graph breaks.

  • MPS complex ops are probed at runtime — STFT/ISTFT, complex multiply, and the scatter op are probed once per device; supported spectral work stays on-device, otherwise the legacy CPU hop is preserved. AUDIO_SEPARATOR_FORCE_CPU_COMPLEX=1 forces the legacy path for diagnosis. Non-CaC Demucs Wiener masking deliberately stays on CPU on MPS.

  • Bounded MPS accumulation, sized per device — duration-scaled overlap-add/accumulator buffers stay on MPS while their estimated footprint fits a budget, and fall back to CPU beyond it, so long inputs cannot exhaust the Metal working set. The budget is half of the free working set — recommended_max_memory() - driver_allocated_memory() — floored at 1 GiB. Model weights are already resident when the decision is made, so the buffers are measured against what is actually left, and can never take more room than they leave behind for activations. driver_allocated_memory() counts the allocator's cached blocks, so free room is understated rather than overstated, and the budget varies with what the process has already allocated. AUDIO_SEPARATOR_MPS_BUFFER_BUDGET_GIB overrides it, and every fallback logs the estimate alongside the budget it was compared against.

    Model inference always runs on MPS. Only the duration-scaled buffers move: for RoFormer the overlap-add result/counter buffers, the Hamming window, and each chunk's output as it is accumulated; for non-RoFormer MDXC the padded mix, its chunk view, and accumulated_outputs; for Demucs the full-track mix and the returned sources. VR and ONNX MDX never allocate these buffers, so the budget does not apply to them.

    Measured on Apple M4 Pro / 24 GB, macOS 15.3.1: Metal reports a 16 GiB working set, and roughly 1 GiB of model weights are resident at the decision point, giving a budget near 7.5 GiB. Input duration at which each path spills, at 44.1 kHz stereo:

    Model / path Stems, settings Spills at
    MelBand / BS-RoFormer (MDXC) 2 instruments, segment 1101 95.1 min
    MDX23C (MDXC, non-RoFormer) 4 stems 63.2 min
    HTDemucs 4 sources, shifts 2 33.1 min
    HTDemucs 4 sources, shifts 1 50.7 min
    htdemucs_ft 4 sources, bag of 4, shifts 2 24.5 min
    VR, MDX (ONNX) n/a

    Note that the spilled path remains unmeasured — the 99 s benchmark input estimates 0.13 GiB for a 2-stem RoFormer and 0.37 GiB for HTDemucs, so no timing cell in this campaign crossed the budget. The change is covered by unit tests (budget scaling, the 1 GiB floor, env override, and a failing or absent Metal query falling back to the floor), not by a benchmark.

Dependencies and packaging

Published package metadata (what pip users get):

  • torch>=2.13,<3 on macOS arm64 only (Torch 2.13 Apple Silicon wheels target macOS 14+); all other platforms keep torch>=2.3,<3.
  • requires-python = ">=3.10,!=3.14.1" (3.14.1 is excluded by the Python metadata of torchvision 0.28, which the Python 3.14 wheel set needs).
  • packaging is now a declared dependency (it was already imported and always present transitively).
  • Extras (cpu / gpu / dml) are unchanged.

Contributor lock (what poetry install gets):

  • The lock uses Poetry 2 (lock-version 2.1), so contributors need Poetry ≥ 2.0; CI already installs current Poetry via pipx.
  • The lock resolves Torch 2.13.0 on Linux (CUDA 13.0 stack) and macOS arm64 — the validated baseline below — while the Windows / Intel-macOS development lock stays on Torch 2.8 for Python < 3.14.
  • ⚠️ Self-hosted Linux GPU runners: CUDA 13 wheels require an R580-or-newer NVIDIA driver. Please check nvidia-smi on the integration runners before merging. This applies to the contributor lock only; published metadata still allows Torch 2.3+ on Linux.

Measured results

Method. Warm steady state per cell: one excluded warm-up separation, then the median of three timed separate() calls. Timed work includes input decode, all architecture-internal work inside separate() (for HTDemucs that includes its per-call network build, checkpoint read, and release), inference, WAV output, and device synchronization. Runner setup, Separator construction, and top-level load_model() are excluded — cold-start latency (including compile warm-up) is not measured. 76 formal cells (38 per accelerator) all used the identical input file; every percentage below compares two cells with identical device, input, model, precision, Python version, and cooldown protocol, and cells from different cooldown protocols are never combined or ranked. Absolute seconds must not be compared between MPS and CUDA — the hardware differs.

Cells Hardware v0.44.5 this PR Python
MPS Apple M4 Pro, macOS 15.3.1 Torch 2.8.0 Torch 2.13.0 3.12
CUDA Google Colab Tesla T4 Torch 2.8.0+cu128 Torch 2.13.0+cu130 3.12

Each side was installed from its own lock, so v0.44.5-vs-PR numbers are the combined code + dependency effect. (The separately-run linear-attention fixture cells are the one exception to the MPS Python version; that split is explained where the fixture is introduced, and no comparison crosses Python versions.)

Five released models, treated as peers:

Model File Settings
Kim MelBand RoFormer mel_band_roformer_kim_ft2_bleedless_unwa.ckpt segment 1101, overlap 8, batch 1
Karaoke MelBand RoFormer mel_band_roformer_karaoke_gabox_v2.ckpt segment 1101, overlap 8, batch 1
BS-RoFormer Vocals Revive bs_roformer_vocals_revive_unwa.ckpt segment 1101, overlap 8, batch 1
HTDemucs htdemucs.yaml shifts 2, overlap 0.25, split on
VR DeEcho UVR-DeEcho-DeReverb.pth window 320, aggression 50, TTA on

v0.44.5 vs this PR, warm eager (median seconds; change vs v0.44.5)

MPS (Apple M4 Pro)

Model v0.44.5 fp32 PR fp32 v0.44.5 autocast PR autocast
Kim MelBand 32.249 27.207 (−15.64 %) 28.877 24.114 (−16.49 %)
Karaoke MelBand 32.286 27.139 (−15.94 %) 28.854 24.152 (−16.30 %)
BS-RoFormer 73.250 62.989 (−14.01 %) 63.257 54.138 (−14.41 %)
HTDemucs 15.237 12.399 (−18.63 %) 14.729 11.695 (−20.60 %)
VR DeEcho 13.281 12.848 (−3.26 %) 18.372 13.660 (−25.65 %)

CUDA (Google Colab Tesla T4)

Model v0.44.5 fp32 PR fp32 v0.44.5 autocast PR autocast
Kim MelBand 25.081 23.980 (−4.39 %) 11.111 10.221 (−8.01 %)
Karaoke MelBand 26.600 23.928 (−10.05 %) 10.765 10.209 (−5.17 %)
BS-RoFormer 62.873 58.036 (−7.69 %) 22.730 21.618 (−4.89 %)
HTDemucs 11.476 11.448 (−0.24 %) 9.057 9.295 (+2.63 % slower)
VR DeEcho 24.208 23.173 (−4.27 %) 19.555 18.724 (−4.25 %)

HTDemucs and VR are not targets of native fp16 or regional compilation; their deltas here are the eager-path + dependency effect only.

Within this PR: released RoFormer precision × compile matrix

Every cell below runs this branch — this table compares execution modes within the PR, not v0.44.5 vs PR. Values are median seconds; parenthesized deltas compare compile against same-precision eager.

MPS

Model fp32 eager fp32 compile autocast eager autocast compile fp16 eager fp16 compile
Kim MelBand 27.207 17.974 (−33.93 %) 24.114 21.317 (−11.60 %) 23.932 21.483 (−10.24 %)
Karaoke MelBand 27.139 17.957 (−33.83 %) 24.152 21.523 (−10.88 %) 24.047 21.541 (−10.42 %)
BS-RoFormer 62.989 46.283 (−26.52 %) 54.138 53.899 (−0.44 %) 55.091 51.770 (−6.03 %)

CUDA (Google Colab Tesla T4)

Model fp32 eager fp32 compile autocast eager autocast compile fp16 eager fp16 compile
Kim MelBand 23.980 19.610 (−18.22 %) 10.221 6.225 (−39.09 %) 9.771 5.552 (−43.18 %)
Karaoke MelBand 23.928 21.238 (−11.24 %) 10.209 5.951 (−41.71 %) 9.426 5.812 (−38.34 %)
BS-RoFormer 58.036 51.141 (−11.88 %) 21.618 13.700 (−36.63 %) 21.274 13.827 (−35.01 %)

The fastest measured condition (bold) differs by accelerator: on MPS it was fp32 + compile for all three RoFormers; on CUDA the fp16-family (autocast or native fp16) combined with compile won, with the exact winner model-dependent. All 36 PR RoFormer cells (2 devices × 3 models × 3 precisions × 2 execution modes) reported effective settings identical to the requested ones, and all 24 compile logs show zero graph breaks, zero regional-compile failures, and zero eager fallbacks.

Linear-attention architecture fixture (not a released model)

To exercise the linear_transformer_depth > 0 code path, a depth-1 linear-attention variant was derived from the released BS-RoFormer checkpoint. It is not a released or trained model, so it carries no separation-quality claim and is kept out of the released-model tables. v0.44.5 fails to load the fixture on both accelerators with the scale TypeError above; this PR runs all 12 cells:

Device fp32 eager → compile autocast eager → compile fp16 eager → compile
MPS 86.514 → 62.582 (−27.66 %) 80.013 → 72.887 (−8.91 %) 77.573 → 72.408 (−6.66 %)
CUDA T4 79.643 → 71.863 (−9.77 %) 30.413 → 19.598 (−35.56 %) 29.037 → 19.246 (−33.72 %)

Native fp16 and memory

After warm eager runs on MPS, retained RoFormer model tensors roughly halve versus autocast, and post-run MPS allocator usage shrinks accordingly:

Model autocast retained tensors native fp16 retained tensors reduction
Kim / Karaoke MelBand 912,913,268 B 456,507,692 B 49.99 %
BS-RoFormer 639,032,384 B 319,516,328 B 50.00 %

Whole-process peak RSS did not decrease in these isolated runs (allocator caches, compiler/runtime areas, and temporaries dominate), so the claim is limited to retained model tensors and post-run device allocation.

Numerical parity

Waveform comparisons across execution modes of the same checkpoint were all valid: 32 comparisons on MPS (minimum finite SNR 43.25 dB, minimum correlation 0.99998) and 26 selected comparisons on CUDA (minimum finite SNR 53.90 dB, minimum correlation 0.999998). CUDA coverage is representative rather than exhaustive. This is execution-mode numerical parity only — it is not ground-truth SDR or listening quality.

Scope notes

  • CPU cells for one MelBand model validated policy resolution and fallback behavior only (fp32/autocast × eager/compile resolve as requested; a native-fp16 request falls back to fp32 with a warning, independent of the compile axis). They are not a performance claim.
  • MDX and MDX23C received compatibility smoke checks only (all matched expectations); this PR makes no performance claims for them.

Verification

  • Unit + contract suites: 471 passed, 5 skipped.
  • Relevant integration selection (RoFormer, output formats, DirectML policy): 11 passed, 21 platform-or-fixture skips; all three 24-bit output cases pass; the ensemble-preset case passes with a valid checkpoint retrieved.
  • Wheel and sdist builds and poetry check --lock pass.
  • README documents all new flags, the verified-combination table, fallback behavior, the reuse/lifecycle semantics, and the contributor-lock driver note.

Known limitations

  • Warm medians only; cold start (including first-run compile cost) is not measured and is documented as a first-run cost.
  • The v0.44.5-vs-PR numbers are own-lock comparisons: combined code + Torch 2.8→2.13 effect, deliberately not attributed per factor.
  • Windows and Intel macOS keep working through explicit gates (compile requires Torch ≥ 2.6, fp16 is device-gated) but were not performance-tested here.
  • DirectML: the policy fallbacks are unit-tested and the existing DML inference path is unchanged, but no DirectML hardware run was part of this campaign.
  • MPS timings are cooldown-controlled but still subject to host thermal/scheduler variance; 3-run medians reduce, not eliminate, it.

Summary by CodeRabbit

  • New Features
    • Added native FP16 and Torch compilation options for supported devices and models.
    • Improved Apple Silicon MPS and DirectML performance with adaptive memory handling and CPU fallbacks.
    • Models are reused when possible, with force-reload support and effective runtime settings available.
    • Improved short-audio processing, chunk scheduling, and model loading reliability.
  • Bug Fixes
    • Prevented false-success CLI results when no output files are produced.
    • Improved cleanup and error handling after failed separation.
  • Documentation
    • Expanded setup, configuration, CLI, acceleration, compatibility, and runtime guidance, including Python 3.14.1 incompatibility.

ntamotsu and others added 15 commits July 28, 2026 16:07
- eliminate redundant RoFormer tail chunks and reuse consecutive model loads

- keep supported MPS spectral work and bounded accumulators on-device

- add observable precision modes and regional compilation with safe fallbacks

- preserve float32 numerical islands and scaled BS-RoFormer attention
- publish platform-aware requirements through Poetry 2 and PEP 621 metadata

- require PyTorch 2.13 on Apple arm64 while preserving the existing 2.8 lock on other Python <3.14 platforms

- use the first torch and torchvision pair with CPython 3.14 wheels and mirror torchvision's Python 3.14.1 exclusion
- document Apple Silicon MPS spectral paths, bounded buffers, and the PyTorch baseline

- explain precision and regional compilation capabilities and fallbacks

- describe effective-mode reporting and consecutive model reuse
- align the contributor CUDA environment with the validated runtime\n- preserve the existing published range and Windows development lock
- Forward linear_transformer_depth through the normalized loader path.
- Preserve zero-depth behavior for existing BS-RoFormer configurations.
- Cover string normalization and constructor forwarding with unit tests.
- Name the pinned rotary-embedding-torch 0.6.5 behavior precisely\n- Link the still-open upstream device-hardcoding issue\n- Document why audio-separator keeps rotary angle construction in float32
- Explain when reused model weights remain allocated or are replaced.
- Document the intentionally per-separation Demucs network lifecycle.
- declare packaging as a direct runtime dependency
- document the CUDA 13 driver floor for the contributor lock
- clarify the locked rotary dependency and fallback warning
- Derive the budget from the free Metal working set instead of a constant
- Keep the 1 GiB floor when Metal cannot report a working-set size
- Add AUDIO_SEPARATOR_MPS_BUFFER_BUDGET_GIB to override the heuristic
- Name the buffers that move to CPU in the fallback logs and the README

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
- Replace the MPS buffer budget internals with the threshold and its override
- Drop the rotary-embedding-torch pinning rationale and the compile retry mechanics
- Merge the duplicate VR/Demucs rows in the verified-combination table

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Aug 2, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 3c9ce46d-2118-41e6-9ba2-22c1e819fae7

📥 Commits

Reviewing files that changed from the base of the PR and between 43e2c2e and 0b6771c.

📒 Files selected for processing (3)
  • .github/workflows/run-unit-tests.yaml
  • tests/unit/test_bs_roformer_fp16.py
  • tests/unit/test_mps_native_fp16.py
🚧 Files skipped from review as they are similar to previous changes (2)
  • tests/unit/test_bs_roformer_fp16.py
  • tests/unit/test_mps_native_fp16.py

Walkthrough

The PR adds execution-policy resolution for autocast, native FP16, and regional Torch compilation. It adds device capability probes, CPU fallbacks, memory-aware accumulation, model reuse, cleanup handling, CLI options, packaging updates, documentation, and tests.

Changes

Execution and device optimization

Layer / File(s) Summary
Execution policy contracts and configuration
audio_separator/separator/execution_policy.py, audio_separator/separator/common_separator.py, audio_separator/separator/separator.py
Adds precision and compilation policies, constructor validation, policy resolution, and effective execution properties.
Device capability detection and MPS memory budgeting
audio_separator/separator/uvr_lib_v5/device_utils.py, audio_separator/separator/uvr_lib_v5/demucs/*
Adds runtime capability probes, CPU fallback decisions, and MPS buffer budgeting.
Complex operation fallbacks and dtype preservation
audio_separator/separator/uvr_lib_v5/stft.py, audio_separator/separator/uvr_lib_v5/tfc_tdf_v3.py, audio_separator/separator/uvr_lib_v5/roformer/*, audio_separator/separator/uvr_lib_v5/demucs/*
Updates spectral operations, normalization, rotary embeddings, and attention for device-specific fallback and low-precision execution.
Demucs and RoFormer inference with buffers and compilation
audio_separator/separator/architectures/demucs_separator.py, audio_separator/separator/architectures/mdxc_separator.py
Adds memory estimation, device-aware accumulation, native FP16 handling, validated chunk scheduling, regional compilation, and cleanup paths.
Model reuse, VR lazy loading, and cleanup
audio_separator/separator/separator.py, audio_separator/separator/architectures/vr_separator.py, tests/unit/test_model_reuse.py
Adds matching-model reuse, forced reloads, rollback behavior, lazy VR loading, retry handling, and reliable separation cleanup.

CLI, packaging, and documentation

Layer / File(s) Summary
CLI options, packaging metadata, and configuration
audio_separator/utils/cli.py, pyproject.toml, audio_separator/separator/roformer/*
Adds native FP16 and Torch compilation flags, Poetry 2 and PEP 621 metadata, platform-specific PyTorch constraints, and RoFormer configuration support.
Documentation and validation
README.md, tests/unit/*
Documents accelerator behavior, precision modes, compilation, fallback rules, model reuse, and runtime properties. Tests cover policy resolution, device utilities, MPS paths, compilation, model reuse, numerical output, and API compatibility.
macOS dependency installation retry
.github/workflows/run-unit-tests.yaml
Retries Poetry installation twice after the initial failure and exits unsuccessfully after the third failure.

Estimated code review effort: 4 (Complex) | ~75 minutes

Poem

A rabbit checks each tensor’s flight,
FP16 glows in MPS light.
Buffers move when memory is tight,
Eager paths guard compile at night.
Models load, reuse, and clean—
Tests keep every route routine.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 17.13% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the PR’s main changes: native FP16, regional torch.compile, RoFormer/MPS optimization, and the PyTorch 2.13 baseline.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (14)
tests/unit/test_model_reuse.py (2)

314-314: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Replace the assigned lambda with a def.

Ruff reports E731 for this line. Use a named function so the lint passes.

♻️ Proposed change
 def test_vr_model_retries_after_weight_loading_failure():
-    placeholder = lambda: None
+    def placeholder():
+        return None
+
     separator = _make_vr_separator(placeholder)

As per coding guidelines: "Use ruff for code linting and formatting checks".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/unit/test_model_reuse.py` at line 314, Replace the lambda assigned to
placeholder with a named def function named placeholder, preserving its
no-argument, no-op behavior so Ruff E731 is resolved.

Sources: Coding guidelines, Linters/SAST tools


269-269: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Hardcoded /tmp model paths trigger ruff S108 in both new test files. The shared root cause is the use of literal /tmp/... strings as stand-in model paths. Replace them with the pytest tmp_path fixture, which also removes the platform assumption.

  • tests/unit/test_model_reuse.py#L269-L269: accept tmp_path in _make_vr_separator, set separator.model_path from it, and update the matching assertion at line 307. Apply the same change to the /tmp/second.ckpt and /tmp/model.ckpt literals at lines 53, 132, 172, 199, and 223.
  • tests/unit/test_demucs_cleanup.py#L14-L14: accept tmp_path in the test and set separator.model_path from it.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/unit/test_model_reuse.py` at line 269, Replace hardcoded /tmp model
paths with pytest tmp_path fixtures to remove ruff S108 violations and platform
assumptions. In tests/unit/test_model_reuse.py lines 269-269, update
_make_vr_separator to accept tmp_path, derive separator.model_path from it,
update the matching assertion at line 307, and apply the same conversion to
literals at lines 53, 132, 172, 199, and 223. In
tests/unit/test_demucs_cleanup.py lines 14-14, accept tmp_path in the test and
derive separator.model_path from it.

Sources: Coding guidelines, Linters/SAST tools

tests/unit/test_demucs_import.py (1)

5-16: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Remove the imported demucs modules from sys.modules after the test.

monkeypatch.syspath_prepend restores sys.path at teardown, but it does not remove entries from sys.modules. The top-level demucs, demucs.hdemucs, demucs.htdemucs, and demucs.spec modules stay cached for the rest of the session. The same source files are also imported as audio_separator.separator.uvr_lib_v5.demucs.*, so two distinct class objects for HDemucs and HTDemucs remain loaded. Any later isinstance or identity check across the two import paths can then fail depending on test order.

♻️ Proposed change
 import importlib
+import sys
 from pathlib import Path
 
 
 def test_checkpoint_compatible_top_level_demucs_import(monkeypatch):
     """Demucs modules remain importable under checkpoint-compatible top-level names."""
     uvr_lib_path = Path(__file__).resolve().parents[2] / "audio_separator" / "separator" / "uvr_lib_v5"
     monkeypatch.syspath_prepend(str(uvr_lib_path))
+    for name in list(sys.modules):
+        if name == "demucs" or name.startswith("demucs."):
+            monkeypatch.delitem(sys.modules, name)
 
     hdemucs = importlib.import_module("demucs.hdemucs")
     htdemucs = importlib.import_module("demucs.htdemucs")
     spec = importlib.import_module("demucs.spec")

monkeypatch.delitem restores the previous sys.modules state at teardown, which also discards the modules imported inside the test.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/unit/test_demucs_import.py` around lines 5 - 16, Update
test_checkpoint_compatible_top_level_demucs_import to remove the imported
top-level demucs modules from sys.modules via monkeypatch.delitem after
importing them, including demucs, demucs.hdemucs, demucs.htdemucs, and
demucs.spec, so teardown restores the prior module state.
audio_separator/separator/uvr_lib_v5/roformer/mel_band_roformer.py (1)

436-441: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Line 441 is now redundant.

Lines 436-437 align masks to the stft_repr real dtype before both tensors become complex. After line 439, masks and stft_repr therefore already share the same complex dtype, so masks.type(stft_repr.dtype) on line 441 is a no-op. Remove it to keep one dtype-alignment point.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@audio_separator/separator/uvr_lib_v5/roformer/mel_band_roformer.py` around
lines 436 - 441, Remove the redundant masks.type(stft_repr.dtype) call after the
torch.view_as_complex conversions in the mask-processing flow, keeping the
earlier dtype alignment before conversion as the single normalization point.
audio_separator/separator/execution_policy.py (1)

77-97: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Log the device type that the capability lookup used.

Line 78 keys the capability lookup on capability_device_type, but the warning at lines 82-86 reports device_type. For DirectML, the two values can differ, so the warning can name a device that was not checked. Use capability_device_type in the native FP16 warning and in the compile warning at lines 103-108 for consistent diagnostics.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@audio_separator/separator/execution_policy.py` around lines 77 - 97, The
native FP16 unsupported warning and the compile warning should report the device
identifier used for capability lookup. Update the relevant logger calls in the
precision-selection flow, including the block around use_native_fp16 and the
compile warning, to use capability_device_type instead of device_type while
preserving all other behavior.
audio_separator/separator/uvr_lib_v5/device_utils.py (1)

85-98: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Record why the probe fails, and silence the lint rule explicitly.

The probe must catch any backend error, so the broad except Exception is correct here. Two improvements apply:

  1. The result is cached by lru_cache. A transient failure, for example a temporary allocation failure, permanently forces the CPU path for that device. A debug log makes that outcome diagnosable.
  2. Ruff reports BLE001 on line 97. A # noqa: BLE001 with a reason documents the intent.
♻️ Proposed change
-    except Exception:
+    except Exception as error:  # noqa: BLE001 - any backend error means the op is unusable
+        logger.debug("Complex spectral probe failed for %s: %s", device_type, error)
         return False

Add a module-level logger:

import logging

logger = logging.getLogger(__name__)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@audio_separator/separator/uvr_lib_v5/device_utils.py` around lines 85 - 98,
Update the probe’s broad exception handler in the cached device-probing function
to log the caught backend error at debug level before returning False,
preserving the catch-all behavior. Add the module-level logger using
logging.getLogger(__name__), and annotate the broad except with a reasoned #
noqa: BLE001 suppression.

Source: Linters/SAST tools

audio_separator/separator/uvr_lib_v5/roformer/bs_roformer.py (1)

478-483: 🚀 Performance & Scalability | 🔵 Trivial | 💤 Low value

The MPS fallback path copies the spectrum across devices twice.

When x_is_mps is true, line 479 computes the STFT on CPU, line 480 moves stft_repr back to the model device, and line 537 moves it to CPU again for the complex multiply. The intermediate move is only needed so rearrange runs on the device. Keeping stft_repr on CPU until line 543 removes one full-spectrum copy in each direction. This is a performance improvement only; the numerical result does not change.

Also applies to: 536-542

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@audio_separator/separator/uvr_lib_v5/roformer/bs_roformer.py` around lines
478 - 483, Update the x_is_mps/x_is_dml STFT path so stft_repr remains on CPU
after torch.view_as_real instead of being moved to device. Adjust the
corresponding rearrange and complex-multiply flow around stft_repr to keep it
CPU-resident until the existing final transfer, preserving numerical behavior
while removing the redundant device copies.
tests/unit/test_bs_roformer_fp16.py (1)

41-41: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add strict=True to the zip.

Ruff reports B905 here. The two lists come from the same model.modules() traversal, so their lengths always match. strict=True records that invariant and clears the lint finding.

As per coding guidelines: "Use ruff for code linting and formatting checks".

♻️ Proposed change
-    for rotary, frequencies in zip(rotary_modules, rotary_frequencies):
+    for rotary, frequencies in zip(rotary_modules, rotary_frequencies, strict=True):
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/unit/test_bs_roformer_fp16.py` at line 41, Update the zip call in the
rotary/frequency iteration to pass strict=True, recording that rotary_modules
and rotary_frequencies must have matching lengths and resolving Ruff B905
without changing the loop behavior.

Sources: Coding guidelines, Linters/SAST tools

audio_separator/separator/architectures/demucs_separator.py (1)

136-138: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Reset demucs_model_instance to None instead of deleting the attribute.

__init__ sets self.demucs_model_instance = None at line 86. The del at line 138 removes that attribute from the instance, so after the first separate() call any read outside separate() raises AttributeError. Assigning None releases the model reference just as effectively and keeps the attribute contract stable across separations. Update tests/unit/test_demucs_cleanup.py to assert separator.demucs_model_instance is None if you accept this.

♻️ Proposed change
         finally:
-            if hasattr(self, "demucs_model_instance"):
-                del self.demucs_model_instance
+            self.demucs_model_instance = None
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@audio_separator/separator/architectures/demucs_separator.py` around lines 136
- 138, Update the cleanup in the finally block of the separator flow to assign
None to self.demucs_model_instance instead of deleting the attribute, preserving
the attribute initialized by __init__ across repeated separations. Update
tests/unit/test_demucs_cleanup.py to assert demucs_model_instance is None after
cleanup.
audio_separator/separator/architectures/mdxc_separator.py (1)

218-254: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Clarify the private compile-state guard and narrow the lazy retry.

  1. _configure_model_compilation saves and restores transformer._compiled_call_impl, a private attribute. Add a short comment near the guard that PyTorch has no public API to de-compile Module back to its original eager implementation; this prevents a Python 3.11+ upgrade from hiding why the private-path fallback exists.

  2. _run_roformer_model retries the chunk on any Exception when is_torch_compiled is true. Catch only the failures Dynamo might produce, or chain the retry failure with raise ... from exc so the original traceback is not replaced.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@audio_separator/separator/architectures/mdxc_separator.py` around lines 218 -
254, Add a brief comment beside the _compiled_call_impl capability guard in
_configure_model_compilation explaining that PyTorch lacks a public API to
restore a Module’s original eager implementation. In _run_roformer_model, narrow
the retry handler to Dynamo/torch.compile-related failures, or preserve the
original exception by chaining any retry failure with raise-from while retaining
the existing eager fallback behavior.
tests/unit/test_execution_policy.py (1)

10-10: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Rename the compile parameter to avoid shadowing the builtin.

Ruff reports A002 for this argument. Rename it to torch_compile and update the call sites in this file.

♻️ Proposed rename
-def _resolve(*, device="mps", requested_device=None, model="mel_band_roformer", autocast=False, native=False, compile=False, pytorch=True):
+def _resolve(*, device="mps", requested_device=None, model="mel_band_roformer", autocast=False, native=False, torch_compile=False, pytorch=True):
     logger = Mock()
     policy = resolve_execution_policy(
         device=torch.device(device),
         requested_device=torch.device(requested_device) if requested_device else None,
         model_family=model,
         use_autocast=autocast,
         use_native_fp16=native,
-        use_torch_compile=compile,
+        use_torch_compile=torch_compile,

As per coding guidelines: "Use ruff for code linting and formatting checks".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/unit/test_execution_policy.py` at line 10, Rename the compile parameter
in _resolve to torch_compile to avoid shadowing the built-in, and update every
call site in tests/unit/test_execution_policy.py to use the new keyword while
preserving the existing behavior.

Sources: Coding guidelines, Linters/SAST tools

tests/unit/test_mps_native_fp16.py (1)

197-207: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add strict=True to the zip call.

Ruff reports B905. The two sequences are built from the same filtered model.modules() scan, so a length mismatch signals a real defect. strict=True turns that into an explicit error instead of a silent truncation. The other new test file in this PR already uses strict=True.

♻️ Proposed fix
     rotary_modules = [module for module in model.modules() if isinstance(module, RotaryEmbedding)]
-    for rotary, frequencies in zip(rotary_modules, rotary_frequencies):
+    for rotary, frequencies in zip(rotary_modules, rotary_frequencies, strict=True):

As per coding guidelines: "Use ruff for code linting and formatting checks".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/unit/test_mps_native_fp16.py` around lines 197 - 207, Update the zip
call in _half_preserving_rotary_frequencies to use strict=True, preserving the
existing pairing and assignment behavior while raising an error if the rotary
module and saved-frequency sequences differ in length.

Sources: Coding guidelines, Linters/SAST tools

audio_separator/separator/separator.py (1)

1117-1134: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Cleanup errors after a successful separation discard the output files.

The finally block raises cleanup_error when separation succeeded. The caller then loses output_files even though the stems were written to disk. clear_gpu_cache and clear_file_specific_paths are housekeeping steps, so a failure there is not equivalent to a separation failure. Consider logging the cleanup error and returning the output files, or document the current contract explicitly.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@audio_separator/separator/separator.py` around lines 1117 - 1134, The finally
block in the separation flow must not raise cleanup_error after successful
separation, because this discards valid output_files. Update the cleanup
handling around clear_gpu_cache and clear_file_specific_paths to log
housekeeping failures and preserve the successful return of output files;
continue retaining the existing failure-path behavior for separation errors.
tests/unit/test_mps_torch_compile.py (1)

25-38: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Build the transformers inside the test instead of at parametrize time.

The three modules are constructed when pytest collects this file. They are built even when the test skips on PyTorch below 2.6, and the same instances persist for the whole session. torch._dynamo.explain traces them, so shared instances can carry compilation state between runs. Pass factories and call them inside the test body.

♻️ Proposed refactor
 `@pytest.mark.parametrize`(
-    "transformer",
+    "build_transformer",
     [
-        MelBandTransformer(dim=16, depth=1, dim_head=4, heads=2, rotary_embed=RotaryEmbedding(dim=4), flash_attn=True),
-        BSTransformer(dim=16, depth=1, dim_head=4, heads=2, rotary_embed=RotaryEmbedding(dim=4), flash_attn=True),
-        BSTransformer(dim=16, depth=1, dim_head=4, heads=2, flash_attn=True, linear_attn=True),
+        lambda: MelBandTransformer(dim=16, depth=1, dim_head=4, heads=2, rotary_embed=RotaryEmbedding(dim=4), flash_attn=True),
+        lambda: BSTransformer(dim=16, depth=1, dim_head=4, heads=2, rotary_embed=RotaryEmbedding(dim=4), flash_attn=True),
+        lambda: BSTransformer(dim=16, depth=1, dim_head=4, heads=2, flash_attn=True, linear_attn=True),
     ],
     ids=["mel-band", "bs-rotary", "bs-linear"],
 )
-def test_regional_transformer_is_captured_as_one_dynamo_graph(transformer):
+def test_regional_transformer_is_captured_as_one_dynamo_graph(build_transformer):
     if version.parse(torch.__version__.split("+")[0]) < version.parse("2.6"):
         pytest.skip("Regional compilation requires PyTorch 2.6 or newer")
 
+    transformer = build_transformer()
     explanation = torch._dynamo.explain(transformer.eval())(torch.randn(2, 8, 16))
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/unit/test_mps_torch_compile.py` around lines 25 - 38, Replace the
parametrized transformer instances with factory callables, preserving the
existing three configurations and test IDs. In
test_regional_transformer_is_captured_as_one_dynamo_graph, perform the PyTorch
version skip before invoking the selected factory, then construct a fresh
transformer and pass it to torch._dynamo.explain.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@audio_separator/separator/architectures/mdxc_separator.py`:
- Around line 205-214: Update the rotary cache invalidation in the loop over
RotaryEmbedding modules after restoring frequencies: clear both cached_freqs and
cached_freqs_seq_len so subsequent lookups cannot reuse stale angles or cache
metadata.

In `@audio_separator/separator/uvr_lib_v5/roformer/rotary.py`:
- Around line 12-48: Pin the rotary-embedding-torch dependency to the 0.6.5
implementation required by _float32_frequencies and rotate_queries_or_keys,
rather than allowing arbitrary 0.6.x patches; update the rotate_queries_or_keys
docstring to document that these helpers rely on internal rotary-embedding-torch
attributes and the pinned dependency behavior.

In `@tests/unit/test_roformer_rotary.py`:
- Line 43: Update the exact-equality torch.testing.assert_close assertions
comparing rotate_queries_or_keys with _float32_reference at the referenced
locations to use a small nonzero tolerance, including both rtol and atol as
appropriate. Apply the same tolerance consistently at all three assertion sites
while preserving the existing comparisons.

---

Nitpick comments:
In `@audio_separator/separator/architectures/demucs_separator.py`:
- Around line 136-138: Update the cleanup in the finally block of the separator
flow to assign None to self.demucs_model_instance instead of deleting the
attribute, preserving the attribute initialized by __init__ across repeated
separations. Update tests/unit/test_demucs_cleanup.py to assert
demucs_model_instance is None after cleanup.

In `@audio_separator/separator/architectures/mdxc_separator.py`:
- Around line 218-254: Add a brief comment beside the _compiled_call_impl
capability guard in _configure_model_compilation explaining that PyTorch lacks a
public API to restore a Module’s original eager implementation. In
_run_roformer_model, narrow the retry handler to Dynamo/torch.compile-related
failures, or preserve the original exception by chaining any retry failure with
raise-from while retaining the existing eager fallback behavior.

In `@audio_separator/separator/execution_policy.py`:
- Around line 77-97: The native FP16 unsupported warning and the compile warning
should report the device identifier used for capability lookup. Update the
relevant logger calls in the precision-selection flow, including the block
around use_native_fp16 and the compile warning, to use capability_device_type
instead of device_type while preserving all other behavior.

In `@audio_separator/separator/separator.py`:
- Around line 1117-1134: The finally block in the separation flow must not raise
cleanup_error after successful separation, because this discards valid
output_files. Update the cleanup handling around clear_gpu_cache and
clear_file_specific_paths to log housekeeping failures and preserve the
successful return of output files; continue retaining the existing failure-path
behavior for separation errors.

In `@audio_separator/separator/uvr_lib_v5/device_utils.py`:
- Around line 85-98: Update the probe’s broad exception handler in the cached
device-probing function to log the caught backend error at debug level before
returning False, preserving the catch-all behavior. Add the module-level logger
using logging.getLogger(__name__), and annotate the broad except with a reasoned
# noqa: BLE001 suppression.

In `@audio_separator/separator/uvr_lib_v5/roformer/bs_roformer.py`:
- Around line 478-483: Update the x_is_mps/x_is_dml STFT path so stft_repr
remains on CPU after torch.view_as_real instead of being moved to device. Adjust
the corresponding rearrange and complex-multiply flow around stft_repr to keep
it CPU-resident until the existing final transfer, preserving numerical behavior
while removing the redundant device copies.

In `@audio_separator/separator/uvr_lib_v5/roformer/mel_band_roformer.py`:
- Around line 436-441: Remove the redundant masks.type(stft_repr.dtype) call
after the torch.view_as_complex conversions in the mask-processing flow, keeping
the earlier dtype alignment before conversion as the single normalization point.

In `@tests/unit/test_bs_roformer_fp16.py`:
- Line 41: Update the zip call in the rotary/frequency iteration to pass
strict=True, recording that rotary_modules and rotary_frequencies must have
matching lengths and resolving Ruff B905 without changing the loop behavior.

In `@tests/unit/test_demucs_import.py`:
- Around line 5-16: Update test_checkpoint_compatible_top_level_demucs_import to
remove the imported top-level demucs modules from sys.modules via
monkeypatch.delitem after importing them, including demucs, demucs.hdemucs,
demucs.htdemucs, and demucs.spec, so teardown restores the prior module state.

In `@tests/unit/test_execution_policy.py`:
- Line 10: Rename the compile parameter in _resolve to torch_compile to avoid
shadowing the built-in, and update every call site in
tests/unit/test_execution_policy.py to use the new keyword while preserving the
existing behavior.

In `@tests/unit/test_model_reuse.py`:
- Line 314: Replace the lambda assigned to placeholder with a named def function
named placeholder, preserving its no-argument, no-op behavior so Ruff E731 is
resolved.
- Line 269: Replace hardcoded /tmp model paths with pytest tmp_path fixtures to
remove ruff S108 violations and platform assumptions. In
tests/unit/test_model_reuse.py lines 269-269, update _make_vr_separator to
accept tmp_path, derive separator.model_path from it, update the matching
assertion at line 307, and apply the same conversion to literals at lines 53,
132, 172, 199, and 223. In tests/unit/test_demucs_cleanup.py lines 14-14, accept
tmp_path in the test and derive separator.model_path from it.

In `@tests/unit/test_mps_native_fp16.py`:
- Around line 197-207: Update the zip call in
_half_preserving_rotary_frequencies to use strict=True, preserving the existing
pairing and assignment behavior while raising an error if the rotary module and
saved-frequency sequences differ in length.

In `@tests/unit/test_mps_torch_compile.py`:
- Around line 25-38: Replace the parametrized transformer instances with factory
callables, preserving the existing three configurations and test IDs. In
test_regional_transformer_is_captured_as_one_dynamo_graph, perform the PyTorch
version skip before invoking the selected factory, then construct a fresh
transformer and pass it to torch._dynamo.explain.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: b9c42467-e3e0-4b7f-bd08-d540580549ad

📥 Commits

Reviewing files that changed from the base of the PR and between 4fe3540 and a61e7e0.

⛔ Files ignored due to path filters (1)
  • poetry.lock is excluded by !**/*.lock
📒 Files selected for processing (38)
  • README.md
  • audio_separator/separator/architectures/demucs_separator.py
  • audio_separator/separator/architectures/mdx_separator.py
  • audio_separator/separator/architectures/mdxc_separator.py
  • audio_separator/separator/architectures/vr_separator.py
  • audio_separator/separator/common_separator.py
  • audio_separator/separator/execution_policy.py
  • audio_separator/separator/roformer/configuration_normalizer.py
  • audio_separator/separator/roformer/roformer_loader.py
  • audio_separator/separator/separator.py
  • audio_separator/separator/uvr_lib_v5/demucs/hdemucs.py
  • audio_separator/separator/uvr_lib_v5/demucs/htdemucs.py
  • audio_separator/separator/uvr_lib_v5/demucs/spec.py
  • audio_separator/separator/uvr_lib_v5/device_utils.py
  • audio_separator/separator/uvr_lib_v5/roformer/attend.py
  • audio_separator/separator/uvr_lib_v5/roformer/bs_roformer.py
  • audio_separator/separator/uvr_lib_v5/roformer/mel_band_roformer.py
  • audio_separator/separator/uvr_lib_v5/roformer/rotary.py
  • audio_separator/separator/uvr_lib_v5/stft.py
  • audio_separator/separator/uvr_lib_v5/tfc_tdf_v3.py
  • audio_separator/utils/cli.py
  • pyproject.toml
  • tests/unit/test_bs_roformer_fp16.py
  • tests/unit/test_cli.py
  • tests/unit/test_configuration_normalizer.py
  • tests/unit/test_demucs_cleanup.py
  • tests/unit/test_demucs_import.py
  • tests/unit/test_device_utils.py
  • tests/unit/test_execution_policy.py
  • tests/unit/test_mdxc_roformer_chunk_starts.py
  • tests/unit/test_model_reuse.py
  • tests/unit/test_mps_device_accumulation.py
  • tests/unit/test_mps_native_fp16.py
  • tests/unit/test_mps_stft_helpers.py
  • tests/unit/test_mps_torch_compile.py
  • tests/unit/test_roformer_dml_forward.py
  • tests/unit/test_roformer_rotary.py
  • tests/unit/test_separator_api_compatibility.py

Comment thread audio_separator/separator/architectures/mdxc_separator.py
Comment thread audio_separator/separator/uvr_lib_v5/roformer/rotary.py
Comment thread tests/unit/test_roformer_rotary.py
ntamotsu and others added 2 commits August 4, 2026 23:52
- Hosted CI Macs expose a paravirtual Metal device (VirtualMac*) whose
  half-precision accumulation cannot meet the 30 dB gate
- Detect virtualization via hw.model so real Apple GPUs keep the gates

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 94011c06-5dfe-4b06-b046-006289dd82ea

📥 Commits

Reviewing files that changed from the base of the PR and between a61e7e0 and b51b082.

📒 Files selected for processing (3)
  • audio_separator/separator/uvr_lib_v5/roformer/rotary.py
  • tests/unit/test_bs_roformer_fp16.py
  • tests/unit/test_mps_native_fp16.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • audio_separator/separator/uvr_lib_v5/roformer/rotary.py

Comment thread tests/unit/test_bs_roformer_fp16.py Outdated
- Catch subprocess.TimeoutExpired, which is not an OSError
- Treat a non-zero sysctl exit as not virtualized
- Unknown environments keep the SNR gates active

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@tests/unit/test_bs_roformer_fp16.py`:
- Around line 21-25: Pin the Darwin helper’s sysctl subprocess invocation to the
absolute system executable path instead of relying on PATH lookup. Update the
subprocess.run calls in tests/unit/test_bs_roformer_fp16.py lines 21-25 and
tests/unit/test_mps_native_fp16.py lines 23-27; both sites require the same
direct change while preserving their existing arguments and error handling.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 816275b9-407a-4b15-b4c6-396a8bdb699c

📥 Commits

Reviewing files that changed from the base of the PR and between b51b082 and 43e2c2e.

📒 Files selected for processing (2)
  • tests/unit/test_bs_roformer_fp16.py
  • tests/unit/test_mps_native_fp16.py

Comment thread tests/unit/test_bs_roformer_fp16.py Outdated
ntamotsu and others added 2 commits August 5, 2026 00:54
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Hosted macOS runners dropped the same large wheel download mid-transfer
  in two consecutive runs, cancelling the whole matrix through fail-fast
- Completed downloads are reused from the poetry cache between attempts

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@ntamotsu

ntamotsu commented Aug 4, 2026

Copy link
Copy Markdown
Contributor Author

Note for reviewers: this PR now includes one CI config change (commit 0b6771c), plus a test-side change that alters what CI displays. Flagging both here so they don't hide in the diff.

run-unit-tests.yaml — the macOS poetry install step now retries up to 3 times. Two consecutive runs failed with the same large wheel download dropped mid-transfer (IncompleteRead on llvmlite, ~37 MB), and fail-fast then cancelled the whole macOS matrix each time. Completed downloads are reused from the poetry cache between attempts, so a retry only refetches what's missing. The Ubuntu and Windows steps are unchanged. If you'd rather keep CI config out of this PR, say the word and I'll drop that commit — it's a convenience, not something the rest of the PR depends on.

The four fp16 SNR gates now skip on virtualized Apple GPUs (hw.model reports VirtualMac* on hosted runners). Hosted macOS runners expose a paravirtual Metal device whose half-precision accumulation lands far below what real Apple GPUs produce (~12–16 dB there vs 43+ dB measured on real hardware), so the 30 dB gate cannot discriminate anything meaningful in that environment. On hosted CI these show as 4 skips; they still run on any real Apple silicon machine, including self-hosted runners.

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