Skip to content

Fix MPS bf16 fallback and torchvision dependency in gemma-trainer - #9

Open
keyuchen21 wants to merge 2 commits into
google-gemma:mainfrom
keyuchen21:fix/mps-bf16-and-torchvision-dep
Open

Fix MPS bf16 fallback and torchvision dependency in gemma-trainer#9
keyuchen21 wants to merge 2 commits into
google-gemma:mainfrom
keyuchen21:fix/mps-bf16-and-torchvision-dep

Conversation

@keyuchen21

Copy link
Copy Markdown

Summary

While working through the gemma-trainer skill end-to-end on an Apple Silicon Mac (MPS, no CUDA) — dataset validation, SFT QLoRA fine-tuning of google/gemma-4-E2B-it, and adapter inference — I hit two issues in the training scripts:

  • torch.cuda.is_bf16_supported() is CUDA-only. All four scripts (sft_train.py, dpo_train.py, reward_train.py, distill_dataset.py) pick the compute dtype with torch.bfloat16 if torch.cuda.is_bf16_supported() else torch.float16. On an MPS-only machine this always evaluates to False, silently forcing fp16 even though MPS supports bf16. In practice this fp16 fallback is more numerically fragile under QLoRA — pushing epochs/rank/LR a bit further than the defaults produced NaN adapter weights for me on fp16, while the same run was stable once forced to bf16.
  • Missing torchvision dependency. sft_train.py and dpo_train.py import AutoModelForMultimodalLM/AutoProcessor for the Gemma 4 architecture, but the Gemma 4 image processor requires torchvision even for a text-only SFT/DPO run. Without it you get a confusing ModuleNotFoundError: No module named 'torchvision' several frames deep inside transformers. distill_dataset.py already lists torchvision in its own install hint; the other two scripts didn't.

Changes

  • Added a small supports_bf16() helper (CUDA check first, falls back to torch.backends.mps.is_macos13_or_newer() when MPS is available) and swapped in all torch.cuda.is_bf16_supported() call sites across the four scripts.
  • Wrapped the AutoModelForMultimodalLM/AutoProcessor import in sft_train.py and dpo_train.py with a clear ImportError pointing at pip install torchvision when it fails.

No behavior changes on CUDA machines — supports_bf16() returns the same thing torch.cuda.is_bf16_supported() did there.

Testing

  • python -m py_compile on all four modified files.
  • Ran the patched sft_train.py end-to-end on an Apple Silicon Mac (MPS) against google/gemma-4-E2B-it with a small SFT dataset (--force-hf, QLoRA): training completes, and supports_bf16() now correctly returns True on this machine (bf16=True, fp16=False in the SFTConfig), where it previously returned False unconditionally.
  • Confirmed the same NaN-adapter failure mode I originally hit at higher epochs/rank/LR does not reproduce once bf16 is selected instead of fp16, on the same hardware.

…gemma-trainer

- Replace torch.cuda.is_bf16_supported() with a device-aware supports_bf16()
  helper across sft_train.py, dpo_train.py, reward_train.py, and
  distill_dataset.py. The CUDA-only check always reports False on MPS-only
  machines (Apple Silicon Macs), silently forcing fp16 training/inference,
  which is more prone to NaN losses under QLoRA than bf16 (which MPS does
  support).
- Surface a clear ImportError pointing at `pip install torchvision` in
  sft_train.py and dpo_train.py when AutoModelForMultimodalLM/AutoProcessor
  fail to import, since torchvision is required by the Gemma 4 image
  processor even for text-only fine-tuning and wasn't previously listed as
  a dependency in these two scripts.

Verified end-to-end on an Apple Silicon Mac (MPS, no CUDA): dataset
validation, SFT QLoRA training on google/gemma-4-E2B-it, and adapter
inference all run correctly with these changes, with bf16 now selected
instead of fp16.
@google-cla

google-cla Bot commented Jul 23, 2026

Copy link
Copy Markdown

Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA).

View this failed invocation of the CLA check for more information.

For the most up to date status, view the checks section at the bottom of the pull request.

@keyuchen21

Copy link
Copy Markdown
Author

recheck

@keyuchen21

Copy link
Copy Markdown
Author

@googlebot I signed it!

@keyuchen21

Copy link
Copy Markdown
Author

Reopening to retrigger CLA check

@keyuchen21 keyuchen21 closed this Jul 23, 2026
@keyuchen21 keyuchen21 reopened this Jul 23, 2026
@keyuchen21
keyuchen21 force-pushed the fix/mps-bf16-and-torchvision-dep branch from d7fc27f to 7572190 Compare July 23, 2026 01:20
@keyuchen21

Copy link
Copy Markdown
Author

@bebechien @bebechien @MrLeonix
Hi guys, when you have a chance, could you please take a look at this PR, The available checks have passed, and there are no merge conflicts. Thanks!

@xbillwork

xbillwork commented Jul 30, 2026 via email

Copy link
Copy Markdown

torch.backends.mps.is_macos13_or_newer() checks for macOS 13, but MPS
bf16 support actually requires macOS 14 (confirmed in torch's own
device_interface.py and autocast_mode.py, and in transformers'
is_torch_bf16_gpu_available()). On macOS 13.x Ventura + Apple Silicon,
supports_bf16() was returning True incorrectly, causing bf16 to be
selected on a config torch itself doesn't support it on.
@keyuchen21

Copy link
Copy Markdown
Author

@xbillwork Good catch — you're right, and I've pushed a fix in 77d53e8.

All four copies of the helper now use torch.backends.mps.is_macos_or_newer(14, 0) instead of is_macos13_or_newer().

While verifying this I found the impact is actually more severe than "a dtype torch considers unsupported." transformers/training_args.py validates bf16=True against is_torch_bf16_gpu_available(), whose MPS branch is exactly torch.backends.mps.is_macos_or_newer(14, 0):

if self.bf16 or self.bf16_full_eval:
    if not self.use_cpu and not is_torch_bf16_gpu_available() and not is_torch_xla_available():
        raise ValueError("Your setup doesn't support bf16/gpu. ...")

So on macOS 13.x + Apple Silicon, the old check would have made bf16=True reach SFTConfig/DPOConfig/RewardConfig and raise a hard ValueError — training wouldn't start at all, rather than just running in a fragile dtype. The fix now matches transformers' own threshold exactly, so the two can't disagree.

One note on your citations: on torch 2.13 the first two have changed — MpsInterface.is_bf16_supported() now returns True unconditionally and the macOS < 14 branch in autocast_mode.py is gone. The transformers one still uses is_macos_or_newer(14, 0) (confirmed on 5.14.1), and since that's what actually gates bf16=True, the conclusion holds either way.

Thanks for the careful review.

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.

2 participants