Skip to content

Make training device-portable (XPU/CUDA/CPU)#4

Open
saforem2 wants to merge 1 commit into
NERSC:mainfrom
saforem2:sunspot-xpu-support
Open

Make training device-portable (XPU/CUDA/CPU)#4
saforem2 wants to merge 1 commit into
NERSC:mainfrom
saforem2:sunspot-xpu-support

Conversation

@saforem2

Copy link
Copy Markdown

Summary

Adds a small utils.device helper that auto-detects the active torch accelerator (cuda / xpu / cpu) and exposes:

  • set_device(local_rank) / synchronize() for cross-device parity
  • dist_backend() returning nccl / ccl / gloo as appropriate
  • no-op shims for cuda nvtx + profiler so the training loop runs unchanged on non-cuda accelerators

train.py now uses these helpers in place of hard-coded `torch.cuda.*` calls — CUDA hosts behave identically (nvtx ranges and profiler hooks still real), while XPU/CPU hosts no-op them. AMP autocast / GradScaler device strings and the DDP backend are picked from the active device.

Distributed init falls back to `ezpz.setup_torch()` when WORLD_SIZE isn't already populated (mpiexec on PALS clusters like Aurora/Sunspot don't set env vars the way SLURM/torchrun do). srun (Perlmutter) and torchrun paths are unchanged — they hit the fast path.

Also fixes `utils.logging_utils.slurm_filter`: it crashed with KeyError outside SLURM. Now falls back to RANK / PMI_RANK / PMIX_RANK so the "only rank 0 logs" behavior works under mpiexec too.

Scope

  • train.py and `utils/logging_utils.py` only. `train_mp.py` / `train_mp_graphs.py` and `networks/vit_te.py` (Transformer Engine) are unchanged — the model-parallel + TE paths still target CUDA exclusively.
  • DALI dataloader path is untouched; XPU runs use the pytorch dataloader.

Test plan

  • Sunspot (Intel XPU, PALS), 1 node × 12 XPUs, BF16, pytorch dataloader — ~122 samples/sec, loss converging
  • Sunspot, 2 nodes × 24 XPUs, BF16 — ~93 samples/sec, loss converging over 8 epochs
  • Perlmutter (NVIDIA, SLURM) sanity check — would appreciate someone with access running submit_pm.sh / submit_pm_dp.sh to confirm CUDA behavior is unchanged

Notes

The ezpz dependency is only imported lazily, on the non-SLURM/torchrun branch. CUDA + SLURM users won't see a new requirement.

Adds a small `utils.device` helper that auto-detects the active torch
accelerator (cuda / xpu / cpu) and exposes:

  - `set_device(local_rank)` / `synchronize()` for cross-device parity
  - `dist_backend()` returning nccl / ccl / gloo as appropriate
  - no-op shims for cuda nvtx + profiler so the training loop runs
    unchanged on non-cuda accelerators

`train.py` now uses these helpers in place of hard-coded `torch.cuda.*`
calls — CUDA hosts behave identically (nvtx ranges and profiler hooks
still real), while XPU/CPU hosts no-op them. AMP autocast / GradScaler
device strings and the DDP backend are picked from the active device.

Distributed init falls back to `ezpz.setup_torch()` when WORLD_SIZE
isn't already populated (mpiexec on PALS clusters like Aurora/Sunspot
don't set env vars the way SLURM/torchrun do). srun (Perlmutter) and
torchrun paths are unchanged.

Also fixes `utils.logging_utils.slurm_filter`: it crashed with KeyError
outside SLURM. Now falls back to RANK / PMI_RANK / PMIX_RANK, so the
"only rank 0 logs" behavior works under mpiexec too.

Tested on Sunspot (Intel XPU, PALS): 12 XPUs/node, BF16, pytorch
dataloader. Loss and throughput stable across epochs.
Copilot AI review requested due to automatic review settings June 12, 2026 17:10

Copilot AI 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.

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

This PR improves portability across different accelerator types (CUDA/XPU/CPU) and launchers (SLURM/torchrun/MPI) by centralizing device selection/synchronization and making rank-aware logging/distributed init more robust.

Changes:

  • Extend slurm_filter to recognize multiple rank environment variables (SLURM/PMI/PMIx/torchrun).
  • Add utils/device.py to abstract device detection, synchronization, distributed backend choice, and profiling shims.
  • Update train.py to use the new device abstraction and to support non-env:// (MPI/PALS) distributed setup via ezpz.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 4 comments.

File Description
utils/logging_utils.py Makes logging rank detection work across more launchers/environments.
utils/device.py New device abstraction layer for CUDA/XPU/CPU + profiling/sync shims + backend selection.
train.py Uses the new device helpers and updates distributed initialization logic for env- and MPI-based setups.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread utils/device.py
Comment on lines +7 to +9
import contextlib
import os
import torch
Comment thread train.py

if params.amp_dtype == torch.float16:
scaler = GradScaler("cuda")
scaler = GradScaler(_device.DEVICE_TYPE)
Comment thread train.py
Comment on lines +356 to +363
if os.environ.get("WORLD_SIZE", "").isdigit():
world_size = int(os.environ["WORLD_SIZE"])
world_rank = int(os.environ.get("RANK", "0"))
local_rank = int(os.environ.get("LOCAL_RANK", "0"))
if world_size > 1:
torch.distributed.init_process_group(
backend=_device.dist_backend(), init_method="env://"
)
Comment thread utils/device.py
Comment on lines +46 to +51
def dist_backend() -> str:
if DEVICE_TYPE == "cuda":
return "nccl"
if DEVICE_TYPE == "xpu":
return "ccl"
return "gloo"
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