190 lines
7.0 KiB
Python
190 lines
7.0 KiB
Python
# Copyright (c) 2026 PaddlePaddle Authors. All Rights Reserved.
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
# you may not use this file except in compliance with the License.
|
|
# You may obtain a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
# See the License for the specific language governing permissions and
|
|
# limitations under the License.
|
|
|
|
"""``enable_compat(level=2)`` API dispatch: route ``paddle.*`` (and
|
|
``paddle.Tensor`` methods) to the torch-aligned ``paddle.compat.*`` for
|
|
external callers while paddle-internal callers keep native semantics.
|
|
|
|
The enable/disable/level lifecycle lives in ``paddle.compat.proxy``; this
|
|
module only installs/removes the dispatchers and holds the dispatch state.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import importlib
|
|
import inspect
|
|
import pkgutil
|
|
import sys
|
|
from functools import wraps
|
|
from typing import TYPE_CHECKING, Any
|
|
|
|
if TYPE_CHECKING:
|
|
import types
|
|
from collections.abc import Generator
|
|
|
|
|
|
# (live module, attr name) -> original value for every patched ``paddle.*``
|
|
# symbol, so ``disable_compat()`` can restore the paddle namespace.
|
|
_PADDLE_NAMESPACE_SAVED: dict[tuple[types.ModuleType, str], Any] = {}
|
|
|
|
|
|
def _caller_is_paddle_internal() -> bool:
|
|
"""True when the caller of the dispatched ``paddle.*`` API is a ``paddle``
|
|
module. Paddle's own internals call these APIs with native kwargs/defaults, so
|
|
they (and the compat impls) get native; only external callers see compat."""
|
|
# _getframe(1) = our caller (the dispatch site); its f_back = who called paddle.X.
|
|
frame = sys._getframe(1).f_back
|
|
if frame is None:
|
|
return False
|
|
name = frame.f_globals.get("__name__") or ""
|
|
return name == "paddle" or name.startswith("paddle.")
|
|
|
|
|
|
def dispatch_function(compat_fn: Any) -> Any:
|
|
"""Wrap a native ``paddle`` callable to route external callers to
|
|
``compat_fn`` while compat is enabled; paddle-internal callers and the
|
|
disabled state get the native callable. Installed only under
|
|
``enable_compat(level=2)``; ``disable_compat`` restores the originals,
|
|
so the default hot path is untouched."""
|
|
|
|
def decorator(native_fn: Any) -> Any:
|
|
@wraps(native_fn)
|
|
def dispatcher(*args: Any, **kwargs: Any) -> Any:
|
|
if (
|
|
len(_PADDLE_NAMESPACE_SAVED) > 0
|
|
and not _caller_is_paddle_internal()
|
|
):
|
|
return compat_fn(*args, **kwargs)
|
|
return native_fn(*args, **kwargs)
|
|
|
|
dispatcher.__compat_fn__ = compat_fn
|
|
dispatcher.__native_fn__ = native_fn
|
|
dispatcher.__signature__ = inspect.signature(compat_fn)
|
|
return dispatcher
|
|
|
|
return decorator
|
|
|
|
|
|
def _iter_compat_modules() -> Generator[types.ModuleType, None, None]:
|
|
"""Yield ``paddle.compat`` modules that declare ``__all__``.
|
|
|
|
``pkgutil.walk_packages`` skips the starting package, so the root
|
|
``paddle.compat`` (holding the top-level functions) is yielded explicitly.
|
|
"""
|
|
import paddle.compat
|
|
|
|
if hasattr(paddle.compat, "__all__"):
|
|
yield paddle.compat
|
|
for module_info in pkgutil.walk_packages(
|
|
paddle.compat.__path__,
|
|
paddle.compat.__name__ + ".",
|
|
):
|
|
compat_module = importlib.import_module(module_info.name)
|
|
if not hasattr(compat_module, "__all__"):
|
|
continue
|
|
yield compat_module
|
|
|
|
|
|
def dispatch_class(native_cls: type, compat_cls: type) -> type:
|
|
"""Create a class proxy that selects compat only for external callers."""
|
|
|
|
class _CompatAwareMeta(type(compat_cls)):
|
|
def __call__(cls, *args: Any, **kwargs: Any) -> Any:
|
|
if cls is proxy:
|
|
if (
|
|
len(_PADDLE_NAMESPACE_SAVED) > 0
|
|
and not _caller_is_paddle_internal()
|
|
):
|
|
return compat_cls(*args, **kwargs)
|
|
return native_cls(*args, **kwargs)
|
|
return super().__call__(*args, **kwargs)
|
|
|
|
proxy = _CompatAwareMeta(
|
|
native_cls.__name__,
|
|
(compat_cls,),
|
|
{
|
|
"__module__": native_cls.__module__,
|
|
"__compat_cls__": compat_cls,
|
|
"__native_cls__": native_cls,
|
|
},
|
|
)
|
|
proxy.__signature__ = inspect.signature(compat_cls)
|
|
return proxy
|
|
|
|
|
|
def _patch_tensor_methods() -> None:
|
|
"""Route ``paddle.Tensor.<m>`` to the compat function for the root compat APIs
|
|
that torch also exposes as Tensor methods (max/min/sort/split/unique/...), so
|
|
``x.max(dim=1)`` works torch-style for external callers (native for internal).
|
|
The dispatcher is patched directly like any paddle Tensor method: the
|
|
descriptor protocol forwards the tensor as the first positional argument,
|
|
which is exactly the compat function's ``input`` parameter.
|
|
"""
|
|
import paddle
|
|
import paddle.compat as compat_root
|
|
|
|
for attr_name in getattr(compat_root, "__all__", ()):
|
|
native_method = getattr(paddle.Tensor, attr_name, None)
|
|
if native_method is None:
|
|
continue
|
|
compat_fn = getattr(compat_root, attr_name)
|
|
_PADDLE_NAMESPACE_SAVED[(paddle.Tensor, attr_name)] = native_method
|
|
setattr(
|
|
paddle.Tensor,
|
|
attr_name,
|
|
dispatch_function(compat_fn)(native_method),
|
|
)
|
|
|
|
|
|
def _apply_paddle_namespace_aliases() -> None:
|
|
"""Install caller-aware dispatchers/proxies for every public ``paddle.compat.*``
|
|
symbol that has a ``paddle.*`` counterpart, plus the Tensor methods."""
|
|
if _PADDLE_NAMESPACE_SAVED:
|
|
return
|
|
|
|
for compat_module in _iter_compat_modules():
|
|
target_name = compat_module.__name__.replace(
|
|
"paddle.compat", "paddle", 1
|
|
)
|
|
try:
|
|
target_module = importlib.import_module(target_name)
|
|
except ModuleNotFoundError:
|
|
continue
|
|
for attr_name in compat_module.__all__:
|
|
compat_attr = getattr(compat_module, attr_name)
|
|
current = getattr(target_module, attr_name, None)
|
|
if current is None or current is compat_attr:
|
|
continue
|
|
_PADDLE_NAMESPACE_SAVED[(target_module, attr_name)] = current
|
|
if isinstance(compat_attr, type):
|
|
setattr(
|
|
target_module,
|
|
attr_name,
|
|
dispatch_class(current, compat_attr),
|
|
)
|
|
else:
|
|
setattr(
|
|
target_module,
|
|
attr_name,
|
|
dispatch_function(compat_attr)(current),
|
|
)
|
|
_patch_tensor_methods()
|
|
|
|
|
|
def _restore_paddle_namespace_aliases() -> None:
|
|
"""Undo :func:`_apply_paddle_namespace_aliases`, restoring the paddle namespace."""
|
|
for (target_module, attr_name), original in _PADDLE_NAMESPACE_SAVED.items():
|
|
setattr(target_module, attr_name, original)
|
|
_PADDLE_NAMESPACE_SAVED.clear()
|