Skip to content

Windows & performance fixes - #61

Open
scragnog wants to merge 6 commits into
koda-dernet:mainfrom
scragnog:pr/platform-perf
Open

Windows & performance fixes#61
scragnog wants to merge 6 commits into
koda-dernet:mainfrom
scragnog:pr/platform-perf

Conversation

@scragnog

@scragnog scragnog commented Jul 29, 2026

Copy link
Copy Markdown

Windows & performance fixes

A bundle of small platform and performance fixes, independent of each other and of everything else we'll be submitting.

  • torchcodec dependency: no compatible Windows wheels exist for torch 2.7.x, so pyproject.toml now carries platform markers (linux/darwin only); requirements.txt mirrored to match.
  • CLI startup: lightweight subcommands (--help, settings, history, convert-sidecars, …) no longer import torch/PEFT/Lightning — startup drops from ~10s to well under a second for those paths. convert-sidecars added to the known-subcommand gate set.
  • Audio duration probing: results cached per (path, size, mtime); mp3/m4a/aac read via mutagen header parse instead of libsndfile's embedded mpg123, which was both slower and spamming stderr with libmpg123 id3.c errors on every scan.
  • GUI responsiveness: dataset/audio library scans now run via asyncio.to_thread — a full-library scan (~7s for 2.5k files) previously froze every other GUI request.
  • Windows DataLoader: replace the forced num_workers=0 with a cap of 4 plus prefetch and persistent workers — a large real-world preprocessing/training throughput win on Windows.
  • Stale bytecode: training subprocesses run with PYTHONDONTWRITEBYTECODE=1 so mid-session code updates can't be shadowed by stale .pyc files.
  • Electron: allow in-app navigation to the /theme-editor page (was blocked by the nav allow-list).

All Python files compile clean; GUI smoke-tested on Windows 11.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added CLI help for the convert-sidecars command.
    • Lightweight CLI commands now start without unnecessary hardware checks.
  • Bug Fixes

    • Improved navigation protection while allowing approved app pages and blocking unauthorized destinations.
    • Large dataset and audio scans no longer freeze the interface.
    • Improved audio-duration detection and caching.
  • Performance

    • Improved Windows training worker behavior and data-loading defaults.
    • Training processes now avoid generating unnecessary bytecode files.
    • Improved platform-specific media compatibility.

Rob Work PC and others added 6 commits July 29, 2026 11:22
torchcodec 0.11.0 (resolved by uv from uncapped >=0.9.1) requires
torch 2.11, but Windows is pinned to 2.7.1+cu128. The DLL mismatch
causes an OS-level 'Entry Point Not Found' dialog for
aoti_torch_aten_narrow before Python can catch the ImportError.

No Windows wheels exist for any torchcodec version compatible with
torch 2.7.x (0.3–0.5 are Linux/macOS only).

audio_duration.py already has soundfile, mutagen, and ffprobe
as fallbacks, so torchcodec is not needed on Windows.

Linux keeps torchcodec<=0.10 (compatible with torch 2.10).
macOS keeps torchcodec>=0.5 (flexible for torch 2.9+).
check_compatibility() was importing torch (~2GB), lightning, and PEFT
unconditionally for every CLI invocation, including lightweight commands
like convert-sidecars that only need json+pathlib. On Windows, repeated
invocations accumulated unreleased working set pages, eventually
exhausting all 128GB RAM.

Now lightweight subcommands (convert-sidecars, dataset, tags, settings,
history) skip the compatibility check entirely.
The subcommand was fully implemented (argparse parser, dispatcher, handler)
but missing from the gate check, causing the CLI to launch the wizard/GUI
instead of routing to the converter.
Duration cache keyed by (path, size, mtime); mp3/m4a/aac read via mutagen
to kill libmpg123 stderr spam; dataset/audio scans via asyncio.to_thread
so a full library scan no longer freezes the GUI.

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

- Windows: allow up to 4 persistent DataLoader workers with prefetch
  instead of forcing num_workers=0 (from 1fe7c70)
- Electron: allow in-app navigation to /theme-editor (from 1fe7c70)

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

- training subprocess no longer writes .pyc files, preventing stale
  bytecode after mid-session code updates (from 6eebbcc)
- requirements.txt now carries the same platform markers for torchcodec
  as pyproject.toml (omission in the original torchcodec fix)

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

coderabbitai Bot commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

The changes add Electron destination allowlisting, platform-specific torchcodec constraints, revised Windows training defaults, cached audio duration probing, threaded GUI scans, a subprocess environment setting, and lightweight CLI handling for convert-sidecars.

Changes

Navigation Protection

Layer / File(s) Summary
Allowlisted navigation and window opening
frontend/electron/main.js
Navigation and window-opening requests now allow only same-origin / and /theme-editor paths, while blocked or malformed destinations are logged.

Platform Runtime Behavior

Layer / File(s) Summary
Platform-specific codec constraints
pyproject.toml, requirements.txt
torchcodec now has separate Linux and Darwin version ranges in both dependency manifests.
Windows training worker settings
sidestep_engine/training_defaults.py, sidestep_engine/core/trainer.py
Windows defaults use workers, prefetching, and persistent workers, while training caps configured Windows workers at four.
Cached audio duration resolution
sidestep_engine/data/audio_duration.py
Audio durations are cached by file metadata, with mutagen tried first for selected compressed formats before fallback probes.
Training subprocess environment
sidestep_engine/gui/task_manager.py
Training subprocesses now set PYTHONDONTWRITEBYTECODE alongside unbuffered output.

Asynchronous Library Scanning

Layer / File(s) Summary
Threaded scan routes
sidestep_engine/gui/server.py
Dataset and audio-directory scan endpoints now await scans executed through asyncio.to_thread.

Lightweight CLI Handling

Layer / File(s) Summary
Lightweight subcommand routing
train.py
convert-sidecars is documented and recognized as lightweight, allowing it to bypass compatibility checks and heavy imports.

Estimated code review effort: 3 (Moderate) | ~25 minutes

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title is concise and accurately reflects two major themes in the changeset: Windows-specific fixes and performance improvements.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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.

@scragnog
scragnog marked this pull request as ready for review July 29, 2026 10:54

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 5

🤖 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 `@sidestep_engine/data/audio_duration.py`:
- Around line 23-25: Replace the unbounded _DURATION_CACHE with a bounded LRU
keyed by (path, size, mtime_ns), preserving successful duration entries and
caching dur == 0 results so failed probes are not repeated for the same key.
Update the duration lookup and insertion logic near the cache definition and
lines 66-68 to enforce the configured capacity while retaining existing duration
behavior.

In `@sidestep_engine/gui/server.py`:
- Line 447: Add concrete return annotations to the new route handlers
list_datasets and the handler defined immediately after it, using the response
type they return (such as JSONResponse) to satisfy the project's annotation
policy.

In `@sidestep_engine/training_defaults.py`:
- Around line 141-144: Remove the Windows-specific override in the advanced
training UI flow that converts positive num_workers values to 0. Preserve the
DEFAULT_NUM_WORKERS, DEFAULT_PREFETCH_FACTOR, DEFAULT_PIN_MEMORY, and
DEFAULT_PERSISTENT_WORKERS values from training_defaults.py so GUI-configured
runs pass these defaults through to the trainer without disabling prefetching or
persistent workers.

In `@train.py`:
- Around line 319-326: Update the exception handling around
check_compatibility() in the non-lightweight subcommand path so unexpected
failures are no longer silently discarded. Preserve the non-fatal CLI behavior,
but log the caught exception with traceback, or narrow the handler to only
expected compatibility errors.
- Around line 144-146: Update _is_lightweight_subcommand so it identifies only
the parsed subcommand, rather than matching every token in sys.argv and option
values. Use the existing positional parsing or an argv parser that consumes
option arguments, preserving lightweight behavior for actual lightweight
commands while ensuring values such as “history” or “convert-sidecars” after
training options do not bypass compatibility checks. Add a regression test
covering these training invocations.
🪄 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: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: c5774b37-d15e-49be-94bd-cecb02c9c2ca

📥 Commits

Reviewing files that changed from the base of the PR and between ecd13bd and 0979796.

📒 Files selected for processing (9)
  • frontend/electron/main.js
  • pyproject.toml
  • requirements.txt
  • sidestep_engine/core/trainer.py
  • sidestep_engine/data/audio_duration.py
  • sidestep_engine/gui/server.py
  • sidestep_engine/gui/task_manager.py
  • sidestep_engine/training_defaults.py
  • train.py

Comment on lines +23 to +25
# Duration cache keyed by (path, size, mtime_ns). GUI dataset scans probe
# every file on every call; durations only change when the file does.
_DURATION_CACHE: dict = {}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🚀 Performance & Scalability | 🟠 Major | ⚡ Quick win

Bound the cache and retain failed probe results.

This dict grows forever as files change or disappear, while dur == 0 entries repeatedly execute every fallback—including a possible 10-second ffprobe call—on each scan. Use a bounded LRU keyed by (path, size, mtime_ns) and cache zero results too (or apply a short TTL for negative results).

Also applies to: 66-68

🤖 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 `@sidestep_engine/data/audio_duration.py` around lines 23 - 25, Replace the
unbounded _DURATION_CACHE with a bounded LRU keyed by (path, size, mtime_ns),
preserving successful duration entries and caching dur == 0 results so failed
probes are not repeated for the same key. Update the duration lookup and
insertion logic near the cache definition and lines 66-68 to enforce the
configured capacity while retaining existing duration behavior.

# length of a full library scan (~7 s for 2.5k files).

@app.get("/api/datasets")
async def list_datasets(tensors_dir: str = ""):

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add return annotations to the new route handlers.

Ruff reports missing annotations for both handlers. Add the concrete response type used by these routes, such as -> JSONResponse, to keep the new endpoints consistent with the project’s annotation policy.

Proposed fix
-    async def list_datasets(tensors_dir: str = ""):
+    async def list_datasets(tensors_dir: str = "") -> JSONResponse:

-    async def list_all_datasets():
+    async def list_all_datasets() -> JSONResponse:

Also applies to: 452-452

🧰 Tools
🪛 Ruff (0.16.0)

[warning] 447-447: Missing return type annotation for private function list_datasets

(ANN202)

🤖 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 `@sidestep_engine/gui/server.py` at line 447, Add concrete return annotations
to the new route handlers list_datasets and the handler defined immediately
after it, using the response type they return (such as JSONResponse) to satisfy
the project's annotation policy.

Source: Linters/SAST tools

Comment on lines +141 to +144
DEFAULT_NUM_WORKERS: int = 2 if sys.platform == "win32" else 4
DEFAULT_PREFETCH_FACTOR: int = 2
DEFAULT_PIN_MEMORY: bool = True
DEFAULT_PERSISTENT_WORKERS: bool = sys.platform != "win32"
DEFAULT_PERSISTENT_WORKERS: bool = True

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🚀 Performance & Scalability | 🟠 Major | ⚡ Quick win

Remove the UI override that disables these Windows defaults.

The advanced UI flow still forces every positive Windows num_workers value to 0 (sidestep_engine/ui/flows/train_steps_advanced.py:372-382). Consequently, these defaults never reach the trainer for GUI-configured runs, and config_factory.py disables prefetching and persistent workers again.

-    if IS_WINDOWS and a["num_workers"] > 0:
-        print_message("Warning: Windows detected -- forcing num_workers=0", kind="warn")
-        a["num_workers"] = 0
+    if IS_WINDOWS and a["num_workers"] > 4:
+        print_message("Warning: Windows workers capped at 4 by the trainer", kind="warn")
🤖 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 `@sidestep_engine/training_defaults.py` around lines 141 - 144, Remove the
Windows-specific override in the advanced training UI flow that converts
positive num_workers values to 0. Preserve the DEFAULT_NUM_WORKERS,
DEFAULT_PREFETCH_FACTOR, DEFAULT_PIN_MEMORY, and DEFAULT_PERSISTENT_WORKERS
values from training_defaults.py so GUI-configured runs pass these defaults
through to the trainer without disabling prefetching or persistent workers.

Comment thread train.py
Comment on lines +144 to +146
def _is_lightweight_subcommand() -> bool:
"""True when the CLI targets a subcommand that needs no heavy imports."""
return bool(_LIGHTWEIGHT_SUBCOMMANDS & set(sys.argv[1:]))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Inspect only the actual subcommand, not option values.

_LIGHTWEIGHT_SUBCOMMANDS & set(sys.argv[1:]) misclassifies valid commands such as train --dataset-dir history or train --output-dir convert-sidecars, causing compatibility checks to be skipped for training. Derive the subcommand from the parsed positional value or an argv parser that understands option arguments, and add a regression test for this case.

🤖 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 `@train.py` around lines 144 - 146, Update _is_lightweight_subcommand so it
identifies only the parsed subcommand, rather than matching every token in
sys.argv and option values. Use the existing positional parsing or an argv
parser that consumes option arguments, preserving lightweight behavior for
actual lightweight commands while ensuring values such as “history” or
“convert-sidecars” after training options do not bypass compatibility checks.
Add a regression test covering these training invocations.

Comment thread train.py
Comment on lines +319 to +326
# Skip for lightweight subcommands to avoid importing torch/PEFT/Lightning
# (~2-3 GB RAM) when all we need is json + pathlib.
if not _is_lightweight_subcommand():
try:
from sidestep_engine._compat import check_compatibility
check_compatibility()
except Exception:
pass # never let the compat check itself crash the CLI

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Do not silently suppress compatibility-check failures.

check_compatibility() already handles expected import failures; the outer except Exception: pass hides unexpected bugs and removes diagnostic context. Log the exception with traceback while preserving non-fatal behavior, or narrow the exception type.

🧰 Tools
🪛 Ruff (0.16.0)

[error] 325-326: try-except-pass detected, consider logging the exception

(S110)


[warning] 325-325: Do not catch blind exception: Exception

(BLE001)

🤖 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 `@train.py` around lines 319 - 326, Update the exception handling around
check_compatibility() in the non-lightweight subcommand path so unexpected
failures are no longer silently discarded. Preserve the non-fatal CLI behavior,
but log the caught exception with traceback, or narrow the handler to only
expected compatibility errors.

Source: Linters/SAST tools

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