From 564839b02a212bc31f4fc90444143ced70213825 Mon Sep 17 00:00:00 2001 From: li-lizhe <147392333@qq.com> Date: Wed, 21 Oct 2026 22:24:57 +0800 Subject: [PATCH] fix(train): select XCCL as the XPU distributed backend get_default_dist_backend() returned the historical "nccl" for every device that is not Ascend NPU, but get_accelerator_device_type() does select XPU, so an XPU run reached init_process_group(backend="nccl") and could not start on a build without NCCL. Resolve XPU to XCCL (legacy oneCCL name "ccl" where that is the only probe the build exposes). Fixes #3777. Signed-off-by: li-lizhe <147392333@qq.com> --- funasr/train_utils/device_funcs.py | 22 +++++++++++----------- tests/test_distributed_device_setup.py | 26 +++++++++++++++++++++----- 2 files changed, 32 insertions(+), 16 deletions(-) diff --git a/funasr/train_utils/device_funcs.py b/funasr/train_utils/device_funcs.py index 6cafb8a59..f592e0792 100644 --- a/funasr/train_utils/device_funcs.py +++ b/funasr/train_utils/device_funcs.py @@ -67,12 +67,12 @@ def force_gatherable(data, device): _ACCELERATOR_DEVICE_TYPES = ("cuda", "npu", "xpu") #: Default ``torch.distributed`` backend per accelerator, most preferred first. -#: Only Ascend NPU is listed. Every other device keeps the historical ``"nccl"`` -#: default, exactly as before this change, so the backend this module selects at -#: runtime is always one that has been exercised on real hardware. XPU does use -#: XCCL (the former oneCCL name ``"ccl"``), but selecting that backend is not -#: covered by hardware here and is tracked separately, outside this change. -_ACCELERATOR_DIST_BACKENDS = {"npu": ("hccl",)} +#: Ascend NPU uses HCCL; Intel XPU uses XCCL, whose former oneCCL name ``"ccl"`` +#: is still the only spelling some builds expose. Either spelling is taken from +#: the build's own ``is__available()`` probe where it exists, and the +#: first name of the list is used when the build reports no probe at all. +#: Devices that are not listed keep the historical ``"nccl"`` default. +_ACCELERATOR_DIST_BACKENDS = {"npu": ("hccl",), "xpu": ("xccl", "ccl")} def _accelerator_module(device_type): @@ -161,11 +161,11 @@ def get_default_dist_backend(): selected backend matches the build. A non-CUDA accelerator never falls back to NCCL. This is only the default — an explicit ``backend=...`` still wins. - XPU is deliberately absent: it is a valid ``torch.distributed`` backend - (XCCL / the former oneCCL name ``"ccl"``), but no XPU hardware is available - to this change, so XPU keeps the historical ``"nccl"`` value here rather - than advertising a runtime path nobody can exercise. Selecting XCCL is a - separate follow-up. + Intel XPU resolves to XCCL, or to the legacy oneCCL name ``"ccl"`` on a + build that only exposes ``is_ccl_available()``. It used to keep the + historical ``"nccl"`` here, which on an XPU-only build (no NCCL) made + ``init_process_group`` fail before the process group existed. The explicit + ``backend=...`` argument is unaffected. """ candidates = _ACCELERATOR_DIST_BACKENDS.get(get_accelerator_device_type()) if not candidates: diff --git a/tests/test_distributed_device_setup.py b/tests/test_distributed_device_setup.py index 4695e0757..6efc18a5d 100644 --- a/tests/test_distributed_device_setup.py +++ b/tests/test_distributed_device_setup.py @@ -87,17 +87,33 @@ def test_npu_backend_needs_no_capability_probe(self): with mock.patch.object(device_funcs, "_dist_backend_available", return_value=False): self.assertEqual(device_funcs.get_default_dist_backend(), "hccl") - def test_xpu_keeps_the_historical_nccl_default(self): - """XPU is a valid ``torch.distributed`` backend (XCCL / oneCCL), but this - change has no XPU hardware to exercise it, so it must not advertise XCCL: - the value for an XPU run stays exactly what it was before the change.""" + def test_default_dist_backend_for_xpu_is_xccl(self): + """An XPU run gets XCCL instead of the historical NCCL default, which an + XPU-only build (no NCCL) cannot initialise at all.""" self._patch(xpu=True) + self.assertEqual(device_funcs.get_default_dist_backend(), "xccl") with mock.patch.object( device_funcs, "_dist_backend_available", side_effect=lambda backend: backend == "xccl", ): - self.assertEqual(device_funcs.get_default_dist_backend(), "nccl") + self.assertEqual(device_funcs.get_default_dist_backend(), "xccl") + + def test_xpu_backend_prefers_xccl_over_the_legacy_ccl_name(self): + """When the build exposes both probes, the current XCCL name wins.""" + self._patch(xpu=True) + with mock.patch.object(device_funcs, "_dist_backend_available", return_value=True): + self.assertEqual(device_funcs.get_default_dist_backend(), "xccl") + + def test_xpu_backend_falls_back_to_the_legacy_ccl_name(self): + """A build whose probe only knows the former oneCCL name still resolves.""" + self._patch(xpu=True) + with mock.patch.object( + device_funcs, + "_dist_backend_available", + side_effect=lambda backend: backend == "ccl", + ): + self.assertEqual(device_funcs.get_default_dist_backend(), "ccl") def test_clear_accelerator_cache_dispatches_to_active_backend(self): modules = self._patch(npu=True)