Repository navigation
Support non-CUDA devices (MPS, CPU) in training and evaluation - #1912
Open
andrew-k681 wants to merge 1 commit into
Open
andrew-k681 wants to merge 1 commit into
andrew-k681 wants to merge 1 commit into
Conversation
|
|
On a machine without CUDA, training fails with errors that name tensor
types rather than the missing device:
ValueError: invalid type: 'torch.mps.FloatTensor'
RuntimeError: Expected all tensors to be on the same device,
but found at least two devices, mps:0 and cpu!
There are three distinct assumptions behind this.
1. The legacy `Tensor.type("torch.cuda.FloatTensor")` string API names a
device *and* a dtype together. There is no "torch.mps.FloatTensor", so
any round-trip through `x.type(y.type())` raises. Replaced with
`.to(dtype)` in yolo_head.py, boxes.py and losses.py.
2. Because that API moved device and dtype together, call sites relied on
it for placement. `.to(dtype)` changes only dtype, so the anchor grids
in YOLOXHead -- built by `torch.arange` on CPU -- stayed on CPU while
activations moved to the accelerator. Device is now passed explicitly.
This is the subtle half: fixing only (1) trades one error for another.
3. `torch.cuda.*` is called unconditionally in the trainer and both
evaluators. `set_device` is now guarded (only CUDA has a per-process
device index; MPS and CPU are single-device), autocast and GradScaler
use the generic `torch.amp` API parameterised by device type, and
`torch.cuda.{Half,Float}Tensor` is replaced by explicit dtype+device.
Three of these were found only by running the trainer, not by reading it:
- `gpu_mem_usage()` called `torch.cuda.max_memory_allocated()`
unconditionally, and the trainer logs it every iteration -- so a
non-CUDA run died at its first log line, after training had started.
It now reports MPS allocation where available and returns 0
otherwise; this is only a log line and must not stop a run.
- `YOLOXHead` forced fp32 for the IoU loss through
`torch.cuda.amp.autocast`, now parameterised by the device the
tensors are actually on.
- `--occupy` pre-allocates memory to avoid fragmentation, a
CUDA-specific trick, now gated on device type.
`--fp16` raises on non-CUDA rather than silently constructing a disabled
GradScaler and training at fp32 while the logs claim otherwise.
A new `yolox/utils/device.py` centralises the choice (`get_device_type`,
`get_device`) so call sites ask for "the device" instead of naming a
vendor.
Scope: this makes single-device non-CUDA training work. It does not claim
distributed MPS support; `core/launch.py` is untouched and still CUDA-only.
Verified by fine-tuning YOLOX-Nano at 640x640 on a custom single-class
COCO dataset on Apple Silicon (M4, torch 2.14, MPS): loss decreases,
epochs complete, checkpoints save.
Not verified on CUDA -- no NVIDIA GPU was available. The changes are
intended to be behaviour-preserving there: `.to(dtype)` and
`torch.amp.autocast("cuda")` are the documented equivalents of the calls
they replace, and `get_device()` returns `cuda:{local_rank}` exactly as
before when CUDA is present. That should be confirmed before merge.
andrew-k681
force-pushed
the
feat/non-cuda-device-support
branch
from
September 9, 2026 09:19
6bf78e4 to
282c388
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Support non-CUDA devices (MPS, CPU) in training and evaluation
Problem
On a machine without CUDA, YOLOX training fails with errors that point at tensor
types rather than at the missing device:
Both are symptoms of CUDA being assumed rather than selected. Neither message
mentions a device, which makes this harder to diagnose than it should be.
Root causes
1. The legacy
Tensor.type()string API names a device and a dtype together.x.type(y.type())round-trips through a string like"torch.cuda.FloatTensor".There is no
"torch.mps.FloatTensor", so the call raises. Five call sites inyolo_head.py,boxes.pyandlosses.py.2. Call sites relied on that API for device placement. Because
.type("torch.cuda.FloatTensor")moved device and dtype, replacing it with.to(dtype)alone is not enough: the anchor grids inYOLOXHead, built bytorch.arangeon CPU, then stayed on CPU while activations moved to theaccelerator. This is the subtle half of the fix and the source of the second
error above.
3.
torch.cuda.*is called unconditionally intrainer.py(
set_device,amp.GradScaler,amp.autocast) and in both evaluators(
torch.cuda.HalfTensor/FloatTensor).Changes
yolox/utils/device.pywithget_device_type()andget_device(),preferring CUDA, then MPS, then CPU. Call sites now ask for "the device"
instead of naming a vendor.
.type(...)replaced with.to(dtype), plus explicitdevice=where the oldcall was also doing placement.
torch.cuda.set_device()guarded — only CUDA has a per-process device index;MPS and CPU are single-device, so indexing them is meaningless.
torch.cuda.amp.{GradScaler,autocast}replaced with the generictorch.ampequivalents, parameterised by device type.
--fp16now raises on non-CUDA instead of silently constructing a disabledGradScaler and training at fp32 while the logs claim otherwise.
Testing
Fine-tuned YOLOX-Nano at 640×640 on a custom single-class COCO dataset on Apple
Silicon (M4, torch 2.14, MPS backend). Training runs, loss decreases, checkpoints
save.
Not verified on CUDA — no NVIDIA GPU was available. The changes are intended
to be behaviour-preserving there:
.to(dtype)andtorch.amp.autocast("cuda")are the documented equivalents of the calls they replace, and
get_device()returns
cuda:{local_rank}exactly as before when CUDA is present. That shouldbe confirmed before merge, and I'd appreciate a review pass from someone with a
GPU to hand.
Notes
selected first and formatted identically.
change makes single-device non-CUDA training work, it does not claim
multi-device MPS support.