Coverage for / home / jenkins / .local / lib / python3.10 / site-packages / hyper_parallel / core / dtensor / debug / _collective_tracer.py: 92%
38 statements
« prev ^ index » next coverage.py v7.13.1, created at 2026-10-25 05:45 +0800
« prev ^ index » next coverage.py v7.13.1, created at 2026-10-25 05:45 +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"""Tracer for the collective communication operations of core.dtensor."""
16import threading
17from typing import Any, Callable, Dict
19from hyper_parallel.core.dtensor import _utils
21# Collective functions in hyper_parallel.core.dtensor._utils to intercept.
22_COLLECTIVE_METHODS = (
23 "differentiable_all_gather_concat",
24 "differentiable_all_to_all",
25 "differentiable_all_reduce",
26 "differentiable_reduce_scatter",
27 "differentiable_all_to_all_single",
28 "differentiable_all_to_all_single_async",
29)
32class CollectiveTracer:
33 """Intercepts core.dtensor collective operations via monkey-patching.
35 Args:
36 on_collective_call: Callback invoked after each collective with
37 ``(method_name, args, kwargs, result)``.
38 """
40 _patch_lock = threading.Lock()
42 def __init__(self, on_collective_call: Callable) -> None:
43 """Record the callback and start with nothing patched.
45 Args:
46 on_collective_call: Callback invoked after each collective with
47 ``(method_name, args, kwargs, result)``.
48 """
49 self._callback = on_collective_call
50 self._originals: Dict[str, object] = {}
51 self._wrappers: Dict[str, object] = {}
53 def install(self) -> None:
54 """Replace the ``_utils`` collective functions with tracing wrappers.
56 Names in :data:`_COLLECTIVE_METHODS` that ``_utils`` does not define are
57 skipped, so a missing collective degrades to no tracing rather than an
58 error. Installations are idempotent per name: a second tracer replaces
59 the first tracer's wrapper and saves that wrapper as its own original.
60 """
61 with self._patch_lock:
62 for name in _COLLECTIVE_METHODS:
63 if not hasattr(_utils, name):
64 continue
65 # Save the raw function for exact restoration.
66 original_func = getattr(_utils, name)
67 self._originals[name] = original_func
69 callback = self._callback
70 method_name = name
72 def _make_wrapper(orig: Callable, cb: Callable, mname: str) -> Callable:
73 def wrapper(*args: Any, **kwargs: Any) -> Any:
74 """Call the original collective, then notify the callback.
76 A callback failure must never break the collective, so it
77 is swallowed and the original result returned untouched.
78 """
79 result = orig(*args, **kwargs)
80 try:
81 cb(mname, args, kwargs, result)
82 except Exception: # pylint: disable=W0703
83 pass # Never break production logic
84 return result
85 return wrapper
87 wrapper = _make_wrapper(original_func, callback, method_name)
88 self._wrappers[name] = wrapper
89 setattr(_utils, name, wrapper)
91 def uninstall(self) -> None:
92 """Restore the original ``_utils`` collective functions.
94 Only restores the attributes this tracer installed. If an inner tracer
95 patched the same name afterwards, restoring our saved original would
96 silently strip that tracer's wrapper, so it is left in place.
97 """
98 with self._patch_lock:
99 for name, original_func in self._originals.items():
100 if getattr(_utils, name, None) is self._wrappers.get(name):
101 setattr(_utils, name, original_func)
102 self._originals.clear()
103 self._wrappers.clear()