fix(torch_utils): resolve the device and the collective backend from the active accelerator by li-lizhe · Pull Request #1825 · modelscope/modelscope · GitHub
Skip to content

fix(torch_utils): resolve the device and the collective backend from the active accelerator - #1825

Open
li-lizhe wants to merge 1 commit into
modelscope:masterfrom
li-lizhe:fix/torch-utils-device-agnostic
Open

li-lizhe wants to merge 1 commit into
modelscope:masterfrom
li-lizhe:fix/torch-utils-device-agnostic

Conversation

@li-lizhe

Copy link
Copy Markdown

What & why

modelscope/utils/torch_utils.py resolves devices and the collective backend from CUDA instead of
from the accelerator that is actually available. On an Ascend NPU (torch_npu, backend hccl) — and
even on a plain CPU build — init_dist() / broadcast() / all_gather() raise before doing any work,
so the distributed entry points of the framework are unusable there.

Fixes #1824

Everything below is in that one file:

helper before after
init_dist() default backend: str = 'nccl' default backend=None → resolved from the accelerator (nccl / hccl / xccl / gloo)
_init_dist_pytorch() torch.cuda.set_device(local_rank) unconditionally, before backend is even looked at binds local_rank on the active accelerator
_init_dist_mpi(), _init_dist_slurm() torch.cuda.set_device() / torch.cuda.device_count() same, accelerator-agnostic
broadcast() builds every tensor with device='cuda' uses the active accelerator (npu on Ascend)
set_random_seed() seeds only the CUDA RNG seeds the accelerator RNG as well (torch.npu.manual_seed_all when available)
_serialize_to_tensor() every non-gloo backend mapped to cuda; assert backend in ['gloo', 'nccl'] hccl accepted; the tensor is placed on the backend's device

New small module-level helpers: get_accelerator_device_type(), get_accelerator_device(),
_get_accelerator_module(), _set_accelerator_device(), _accelerator_device_count(),
_default_dist_backend(), _collective_device(). The device type is resolved through
torch.accelerator.current_accelerator() when the PyTorch build provides it (it was added in 2.5),
falling back to probing torch.cuda / torch.npu / torch.xpu / torch.mps; cpu when there is
none. Collectives stay on CPU for gloo, as before.

Deliberately not used: device = 'cuda' if torch.cuda.is_available() else 'cpu'. That shape would
make Ascend users silently fall back to CPU instead of using the NPU; here the accelerator is always
resolved to the real device type. No NPU-specific branch is added to any call site — this is a plain
device-agnostic change, so the CUDA behaviour is untouched.

Verification — Ascend 910B4, real hardware

torchrun --nproc_per_node=2, 2 × Ascend 910B4, torch 2.15.0.dev20260917+cpu + torch_npu,
CANN toolchain. The module under test is transferred byte-identically (md5-checked) and driven by the
same script in both runs, only the module differs (MODE=default = init_dist(launcher='pytorch'),
MODE=hccl = explicit backend='hccl').

before:

[0] module=before.py mode=default
[0] FAIL init_dist(default)         AttributeError: module 'torch._C' has no attribute '_cuda_setDevice'
[0] !! pg not initialized; manual init(hccl) so the rest is still exercised
[0] OK   pg backend                 hccl
[0] OK   npu current_device         0
[0] OK   all_reduce(sum, npu)       2.0
[0] FAIL broadcast(dict, src=0)     AssertionError: Torch not compiled with CUDA enabled
[0] FAIL all_gather(str)            AssertionError: Torch not compiled with CUDA enabled

after:

[0] module=after.py mode=default
[0] device_type=npu
[0] OK   init_dist(default)         None
[0] OK   pg backend                 hccl
[0] OK   npu current_device         0
[0] OK   all_reduce(sum, npu)       2.0
[0] OK   broadcast(dict, src=0)     {'rank': 0, 'tag': 'hello'}
[0] OK   all_gather(str)            ['rank-0', 'rank-1']

(identical output on rank 1, with npu current_device 1; the explicit backend='hccl' case gives the
same before/after split)

Note that before this change even init_dist(launcher='pytorch', backend='hccl') failed: the process
group was created but _init_dist_pytorch() called torch.cuda.set_device() first, so passing the
correct backend was not a workaround.

Regression check on a CPU-only build (torch 2.14.0a0, torch.cuda.is_available() == False,
torchrun --nproc_per_node=2, gloo) — this path was broken as well, not only the NPU one:

before: [0] FAIL init_dist(default)  AttributeError: module 'torch._C' has no attribute '_cuda_setDevice'
        [0] FAIL broadcast(dict)     AssertionError: Torch not compiled with CUDA enabled
after:  [0] device_type=cpu
        [0] OK   init_dist(default)  None
        [0] OK   pg backend          gloo
        [0] OK   broadcast(dict)     {'rank': 0}
        [0] OK   all_gather(str)     ['rank-0', 'rank-1']

Not tested

  • CUDA hardware — no NVIDIA GPU available here. The CUDA path is unchanged by construction
    (get_accelerator_device_type() returns 'cuda' and the default backend resolves to nccl), but I
    could not execute it.
  • MPI / SLURM launchers — only launcher='pytorch' was exercised end to end; the two other helpers
    changed in the same one-line way.
  • XPU / MPS / ROCm — no such hardware; they go through the same helper.
  • Full modelscope pipelines (FunASR / ms-swift / DiffSynth) — not run, their dependency trees are not
    installed on the test machine.

Also noticed, not changed in this PR

_serialize_to_tensor() calls logger.warning(...) on the >1 GB branch, but there is no module-level
logger in torch_utils.py (no logging import, no get_logger() call) — that branch would raise
NameError. I left it alone to keep this PR to a single concern; happy to send a separate one-liner.

…ve accelerator

The distributed helpers assumed CUDA everywhere:
- init_dist() defaulted to backend='nccl' and _init_dist_pytorch() called
  torch.cuda.set_device() unconditionally (crashes on Ascend NPU/hccl and on
  CPU/gloo with AttributeError: module 'torch._C' has no attribute '_cuda_setDevice')
- broadcast() built every tensor with device='cuda'
- _serialize_to_tensor() mapped every non-gloo backend to 'cuda' and asserted
  backend in ['gloo', 'nccl']
- set_random_seed() seeded only the CUDA RNG

Resolve the device type and the default collective backend from the actuator that
is actually available (torch_npu -> 'npu', xpu -> 'xpu', mps -> 'mps', else 'cuda'),
keeping the CUDA behaviour identical.

Signed-off-by: li-lizhe <147392333@qq.com>

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant