Coverage for / home / jenkins / .local / lib / python3.10 / site-packages / hyper_multicore / _automatic.py: 91%
53 statements
« prev ^ index » next coverage.py v7.13.1, created at 2026-11-15 05:03 +0800
« prev ^ index » next coverage.py v7.13.1, created at 2026-11-15 05:03 +0800
1# Copyright 2026 Huawei Technologies Co., Ltd
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14# ============================================================================
15"""Version-isolated adapters for cleanup before Torch communication teardown."""
17from __future__ import annotations
19import atexit
20import functools
21import logging
22from collections.abc import Callable
23from typing import Any, Optional
25import torch.distributed as dist
26import torch.distributed.distributed_c10d as c10d
28from . import _collective
30_LOGGER = logging.getLogger(__name__)
33def exchange(value: Any) -> list[Any]:
34 """Exchange resource metadata on the registered cleanup Root."""
35 return _collective.exchange(
36 value,
37 group=_CLEANUP.root_group,
38 failure_prefix="Multicore metadata exchange failed while converging rank results",
39 )
42def collective_call(
43 callback: Callable[[], None],
44 *,
45 stage: str,
46) -> None:
47 """Converge resource callback failures on the registered cleanup Root."""
48 _collective.collective_call(
49 callback,
50 stage=stage,
51 group=_CLEANUP.root_group,
52 )
55class _Cleanup:
56 """Own a single exit hook and dependencies for the current runtime generation."""
58 def __init__(self) -> None:
59 """Start without native owners or patched framework entry points."""
60 self.callback: Optional[Callable[[], None]] = None
61 self.dependencies: set[Any] = set()
62 self.installed = False
63 self.root_group: Any = None
65 def close(self) -> None:
66 """Keep both the callback and communication available if release fails."""
67 if self.callback is not None:
68 self.callback()
69 self.callback = None
70 self.dependencies.clear()
72 def at_exit(self) -> None:
73 """Best-effort cleanup before torch_npu closes its device runtime."""
74 try:
75 self.close()
76 # Exit must continue through the framework's own device cleanup on failure.
77 except Exception: # pylint: disable=broad-exception-caught
78 _LOGGER.exception("Automatic multicore cleanup failed during process exit")
80 def install(self) -> None:
81 """Intercept the backend boundary, including previously imported destroy aliases."""
82 if self.installed:
83 return
84 if hasattr(c10d.ProcessGroup, "shutdown"):
85 owner, name = c10d.ProcessGroup, "shutdown"
86 elif hasattr(c10d, "_shutdown_backend"):
87 owner, name = c10d, "_shutdown_backend"
88 else:
89 raise RuntimeError("unsupported Torch process-group lifecycle; no automatic cleanup boundary")
90 original = getattr(owner, name)
92 @functools.wraps(original)
93 def shutdown(group: Any, *args: Any, **kwargs: Any) -> Any:
94 """Run cleanup before a dependent communicator is destroyed.
96 Args:
97 group: Communication dependency being destroyed.
98 *args: Original framework arguments.
99 **kwargs: Original framework keyword arguments.
100 """
101 if group in self.dependencies:
102 self.close()
103 return original(group, *args, **kwargs)
105 setattr(owner, name, shutdown)
106 # A real NPU input requires torch_npu initialization before this first binding.
107 atexit.register(self.at_exit)
108 self.installed = True
111_CLEANUP = _Cleanup()
114def register(callback: Callable[[], None], root_group: Any) -> None:
115 """Register native owners without changing the user's process-group API.
117 Args:
118 callback: Collective release operation; failures must preserve ownership.
119 root_group: SHMEM Root dependency and lifecycle metadata group.
120 """
121 _CLEANUP.install()
122 _CLEANUP.root_group = root_group
123 _CLEANUP.callback = callback
124 _CLEANUP.dependencies.update(group for group in (root_group, dist.group.WORLD) if group is not None)