Skip to content

Make the uv environment build on macOS and Linux, and let YORU pick its compute device - #41

Merged
rysk-t merged 4 commits into
mainfrom
env_uv
Sep 4, 2026
Merged

Make the uv environment build on macOS and Linux, and let YORU pick its compute device#41
rysk-t merged 4 commits into
mainfrom
env_uv

Conversation

@rysk-t

@rysk-t rysk-t commented Sep 4, 2026

Copy link
Copy Markdown
Collaborator

uv sync only ever worked on Windows. tool.uv.environments pinned the lockfile to sys_platform == 'win32', so resolution aborted on macOS and on a Linux CUDA host before a single package was considered — and behind that pin sat five more portability defects, each visible only once the previous one was fixed.

Separately, ultralytics resolves an unnamed device CUDA → CPU and never auto-selects MPS. Since YORU had no device plumbing at all, every Apple Silicon user was silently training and inferring on the CPU.

Both are fixed. There is now CI, which would have caught the first one.

Packaging

  • Drop the win32 environment pin; fork the resolution over win32/linux/darwin so one lockfile serves Windows+CUDA, Linux+CUDA and macOS+MPS.
  • Take torch/torchvision from the cu124 index on Windows and Linux only, and from PyPI (the build carrying Metal support) on macOS.
  • Raise requires-python to >=3.10. dearpygui 1.11.1 ships macOS arm64 wheels for cp310+ only, and 3.9 is EOL.
  • Bound numpy to <2. opencv-python 4.10.0.82 is built against the numpy 1.x ABI; on 3.10+ an unbounded numpy resolved to 2.x and broke import cv2.
  • Mark pywin32 win32-only (it is never imported anywhere in the source), drop pyopengl-accelerate (no arm64 wheel, sdist fails to cythonize) and the pyqt5-qt5==5.15.2 override (that version has no arm64 wheel).
  • Tell hatchling the package directory is yoru — it could not infer that from the distribution name yoru_uv, so uv sync failed to build the project on every platform once the lock was regenerated.
  • Relax setuptools<70 to <81. The real constraint is pkg_resources, removed in setuptools 81 and still imported by eel, which is what YORU.yml already documented.

The committed lock had drifted to a different manifest entirely (root package yoru/0.1.0, ultralytics==8.2.52), so uv sync --locked failed everywhere and plain uv sync silently re-locked — nobody was installing the pinned versions. Regenerated, and CI now guards it.

Device selection

New yoru/libs/device.py resolves CUDA → MPS → CPU, honours a YORU_DEVICE override, and degrades with a logged warning when a device is unavailable. Both training scripts gained --device, the training GUI gained a selector with a resolved-device readout, and the inference wrappers now name the device on every predict call.

torchvision's detection heads needed care: before 0.29 they mis-train on MPS. Measured here on Faster R-CNN, same dataset, one epoch:

torchvision device Avg Loss
0.26.0 mps inf
0.29.0 mps 0.5047 / 0.4859 / 0.4218
0.29.0 cpu 0.4883 / 0.4688 / 0.4861

macOS is therefore pinned to torchvision 0.29+, and _resolve_device checks the installed version rather than refusing MPS outright, since the conda environment can still carry an older build.

Also fixed

  • yoru/testing.py was a stub that wrote a b"dummy" checkpoint, so both smoke tests passed in 0.19 s and proved nothing. They now drive the real create_yaml_traintrain_ultralyticsload_yolo_model round trip.
  • tests/test_cuda.py hard-asserted torch.cuda.is_available(), making the suite red on any machine without an NVIDIA GPU. It now skips.
  • yoru/app.py's config-creator launcher used subprocess.CREATE_NEW_CONSOLE, which does not exist off Windows, and was the only GUI not going through the portable _launch_gui helper.
  • yoru/libs/detection.py imported dearpygui but never used it, making the detection module need a GUI toolkit for nothing.
  • The vendored YOLOv5 gated MPS on the deprecated torch.has_mps.

Documentation

The uv route installs everything a Python environment can hold, so what was left
undocumented were the pieces it cannot install. README gains a Prerequisites
section covering them:

  • A Chromium browser. The launcher is served by Eel, which uses its chrome
    mode, so Chrome or Chromium has to be findable (the App Paths\chrome.exe
    registry key on Windows, Google Chrome.app / Chromium.app / mdfind on
    macOS, four binary names on PATH on Linux). Edge is Chromium-based but
    registers as msedge.exe and is not found; Safari is unsupported by Eel. Only
    the launcher needs it -- every other GUI is a native window.
  • An NVIDIA driver. The wheels carry their own CUDA runtime, so the CUDA
    toolkit is optional; what is required is a driver supporting CUDA 12.x
    (527.41+ on Windows, 525.60.13+ on Linux).
  • macOS. Apple Silicon and macOS 14+ only, xcode-select --install before
    uv sync because imgui and gevent have no arm64 wheels and are compiled,
    and the Camera / Input Monitoring / Screen Recording prompts that appear on
    first use.

The uv section is promoted out of the conda quick install, gains the uv
installation commands themselves, and says to run from the repository root,
since the launcher resolves web/ and config/ relative to the working
directory. docs/install.md is restructured around the same prerequisites with
both routes side by side, keeping the existing conda steps.

Also recorded: YORU_DEVICE does not reach YOLOv5 inference, which loads
through torch.hub and picks CUDA or CPU on its own.

Compatibility

Windows and Linux resolve exactly as before — torch 2.6.0+cu124, torchvision 0.21.0+cu124, setuptools 69.5.1. Only the macOS fork moves.

The macOS floor is now 14 (Sonoma, Sept 2023), because torchvision 0.29 requires torch 2.14 whose macOS wheels target macosx_14_0. Reverting that means giving up MPS training for the torchvision backends; YOLO training on MPS is unaffected either way.

Verification

CI is green on ubuntu / windows / macos. On macOS 26.5.2, Apple M5 (arm64), uv 0.11.14:

  • uv sync --locked → Python 3.10.19, torch 2.14.0, torchvision 0.29.0, ultralytics 8.3.222, opencv 4.10.0, numpy 1.24.4, dearpygui 1.11.1
  • pytest → 79 passed, 7 skipped
  • Training via the exact argv train_GUI builds, --device autoDevice: Apple MPS; 30 epochs on 400/100 frames of test_data reached mAP50 0.974 / mAP50-95 0.692
  • Inference through load_yolo_model over the test video: MPS and CPU return identical detections
  • Both smoke tests exercise real training and inference

The GitHub runners have no NVIDIA GPU, so a green Windows leg only proves the environment resolves, installs and imports. That gap is now closed on a real Windows box — Windows 11, RTX 4070 SUPER, uv 0.8.19:

  • uv lock --check clean, uv sync --locked → uv-managed CPython 3.10.18, torch 2.6.0+cu124, torchvision 0.21.0+cu124, ultralytics 8.3.229
  • torch.cuda.is_available() → True; resolve_device("auto")cuda
  • uv run pytest83 passed, 3 skipped
  • Both smoke tests run end to end against test_data/labeled_frames: training (64 train / 16 val, 1 epoch, CPU) writes best.pt in 14 s, and inference through load_yolo_model consumes it
  • Full round trip on CUDA: 30 epochs → mAP50 0.923 / mAP50-95 0.576, and inference over 20 real frames returns 24 detections (conf 0.27-0.98) across both classes

Still unexercised: the GUI device selector was import-checked only, on both platforms — no interactive run — and no closed-loop hardware (NI-DAQ / Arduino) was attached.

rysk-t and others added 2 commits September 4, 2026 13:10
…ts device

`uv sync` only worked on Windows: tool.uv.environments pinned the lockfile to
sys_platform == 'win32', so resolution aborted on macOS and on a Linux CUDA
host before any package was considered. Behind that pin sat five more
portability defects, each only visible once the previous one was fixed.

Packaging:
  - drop the win32 environment pin; fork the resolution over win32/linux/darwin
    so one lockfile serves Windows+CUDA, Linux+CUDA and macOS+MPS
  - take torch/torchvision from the cu124 index on Windows and Linux only, and
    from PyPI (the build carrying Metal support) on macOS
  - raise requires-python to >=3.10: dearpygui 1.11.1 ships macOS arm64 wheels
    for cp310+ only, and 3.9 is EOL
  - bound numpy to <2. opencv-python 4.10.0.82 is built against the numpy 1.x
    ABI, and on 3.10+ an unbounded numpy resolved to 2.x and broke `import cv2`
  - mark pywin32 win32-only (it is never imported), drop pyopengl-accelerate
    (no arm64 wheel, sdist fails to cythonize) and the pyqt5-qt5==5.15.2
    override (that version has no arm64 wheel)
  - tell hatchling the package directory is `yoru`; it could not infer it from
    the distribution name `yoru_uv`, so `uv sync` failed to build the project
    on every platform once the lock was regenerated
  - the committed lock had drifted to a different manifest (root package
    yoru/0.1.0, ultralytics==8.2.52), so `uv sync --locked` failed everywhere
    and plain `uv sync` silently re-locked. Regenerated, and CI now guards it

Device selection: ultralytics never auto-selects MPS -- select_device("")
resolves CUDA then CPU -- so every Apple Silicon user silently trained and
inferred on the CPU. New yoru/libs/device.py resolves CUDA -> MPS -> CPU,
honours YORU_DEVICE, and degrades with a warning when a device is unavailable.
Both training scripts gained --device, the training GUI gained a selector, and
the inference wrappers now name the device on every predict call. torchvision
detection heads are excluded from the MPS default: they do not converge there
(loss diverges to 1e32 against 0.49 on CPU), so `auto` stays CUDA -> CPU and
MPS requires an explicit request.

Also: yoru/testing.py was a stub that wrote a b"dummy" checkpoint, so both
smoke tests passed in 0.19s and proved nothing -- they now drive the real
create_yaml_train -> train_ultralytics -> load_yolo_model round trip (21s).
tests/test_cuda.py skips instead of failing without an NVIDIA GPU.
app.py's config-creator used subprocess.CREATE_NEW_CONSOLE, which does not
exist off Windows. detection.py imported dearpygui but never used it.
First CI: uv sync + pytest on ubuntu/windows/macos, with `uv lock --check`.

Verified on macOS 26.5.2, Apple M5 (arm64), uv 0.11.14:
  uv sync from the committed lock -> Python 3.10.19, torch 2.11.0 (MPS
  available), torchvision 0.26.0, ultralytics 8.3.222, opencv 4.10.0,
  numpy 1.24.4, dearpygui 1.11.1
  pytest: 79 passed, 7 skipped
  training via the exact argv train_GUI builds, --device auto -> "Apple MPS",
  ultralytics confirms "MPS (Apple M5)"; 30 epochs on 400/100 frames of
  test_data reached mAP50 0.974 / mAP50-95 0.692 in 672 s
  inference through load_yolo_model over the test video: MPS and CPU return
  identical detections (120 over 60 frames)
Windows and Linux are verified at the lockfile level only; both still need a
real `uv sync` on hardware.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…orrectly

The previous commit kept "auto" off MPS for the torchvision backends because
their detection heads diverged there. That turns out to be fixed upstream.
Measured on this machine with yoru/libs/train_torchvision.py, Faster R-CNN,
same dataset, one epoch:

    torchvision 0.26.0  mps -> Avg Loss: inf
    torchvision 0.29.0  mps -> 0.5047 / 0.4859 / 0.4218  (three runs)
    torchvision 0.29.0  cpu -> 0.4883 / 0.4688 / 0.4861  (three runs)

So macOS now takes torchvision 0.29 or newer, and _resolve_device checks the
installed version instead of refusing MPS outright -- the conda environment can
still carry an older build, and that case keeps the CPU fallback and its
message.

Two things had to move to get there:

  - override-dependencies = ["torch>=2.0"] replaced torchvision's own
    "torch==2.14.0" requirement, so the first attempt locked torchvision 0.29.0
    against torch 2.11.0. The override is dropped; nothing needed it.
  - setuptools<70 then blocked torch 2.14, which wants >=77.0.3. The real
    constraint is pkg_resources, removed in setuptools 81 and still imported by
    eel -- which is what YORU.yml already documented -- so the bound is now <81,
    matching it. Verified eel still imports.

Windows and Linux are untouched: both still resolve torch 2.6.0+cu124,
torchvision 0.21.0+cu124 and setuptools 69.5.1, byte-identical to 471d274.
macOS moves to torch 2.14.0 / torchvision 0.29.0 / setuptools 80.10.2, which
raises the macOS floor to 14 (Sonoma) since those torch wheels are macosx_14_0.
README updated accordingly.

Verified on macOS 26.5.2, Apple M5 (arm64):
  uv sync --locked; pytest 79 passed, 7 skipped
  torchvision --device auto -> mps, loss 0.5087 -> 0.3454 over two epochs
  ultralytics --device auto -> "Apple MPS", torch 2.14.0, mAP50 0.753 at 3 epochs
  inference via load_yolo_model: mps and cpu both return 120 detections
  over 60 frames
  both smoke tests still exercise real training and inference

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@rysk-t
rysk-t marked this pull request as draft September 4, 2026 05:44
rysk-t and others added 2 commits September 4, 2026 18:36
The uv route now installs everything a Python environment can hold, so what
is left are the pieces it cannot: a Chromium browser for the Eel launcher, an
NVIDIA driver for CUDA, and the Xcode Command Line Tools on macOS. None of
them were written down, and the uv section did not even say how to install uv.

README gains a Prerequisites section covering all three, with what Eel
actually looks for on each OS (Edge registers as msedge.exe and is not found;
Safari is unsupported), the fact that the PyTorch wheels carry their own CUDA
runtime so the toolkit is optional, and the arm64-only wheels and permission
prompts that macOS brings. The uv section is promoted out of the conda quick
install, gets the uv installation commands, and says to run from the
repository root, since the launcher resolves web/ and config/ relatively.

docs/install.md is restructured around the same prerequisites with both
routes side by side, keeping the existing conda steps.

Also records that YORU_DEVICE does not reach YOLOv5 inference, which loads
through torch.hub and picks CUDA or CPU on its own.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01G3rHpfKzAnmYkxhmvHKwiP
The PyTorch wheels ship the CUDA runtime themselves -- cudart, cuBLAS and
cuDNN sit in torch/lib -- so neither route needs the toolkit installed.
Verified on the Windows box: no nvcc on PATH and CUDA_PATH unset, yet
torch.version.cuda is 12.4 and CUDA matmuls run.

The cu118/cu121 choice in the conda steps therefore has to match the driver,
not an installed toolkit. Also notes that the CUDA version nvidia-smi prints
is the driver's ceiling rather than the version in use, which is the usual
source of the confusion.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01G3rHpfKzAnmYkxhmvHKwiP
@rysk-t
rysk-t marked this pull request as ready for review September 4, 2026 09:51
@rysk-t
rysk-t merged commit ec5856d into main Sep 4, 2026
6 checks passed
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