Skip to content
Open
Show file tree
Hide file tree
Changes from 22 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
91b9144
align 3 Tensor api and PReLU
Manfredss Jul 27, 2026
3451263
Merge branch 'develop' of https://github.com/paddlepaddle/paddle into…
Manfredss Jul 27, 2026
d5154f3
fix PReLU, fix enable_compat restore after using guard
Manfredss Jul 27, 2026
4865ecc
add test coverage
Manfredss Jul 28, 2026
8aa81ef
fix per bot feedback
Manfredss Jul 28, 2026
7f2684a
fix typo
Manfredss Jul 28, 2026
7eda286
also fix paddle.distributions.categorical.Categorical
Manfredss Jul 28, 2026
ec4686b
fix
Manfredss Jul 29, 2026
cf76ec4
fix
Manfredss Jul 30, 2026
f668adb
Refine compat levels and guard state restoration
Manfredss Jul 30, 2026
111a665
Merge branch 'develop' of https://github.com/paddlepaddle/paddle into…
Manfredss Jul 30, 2026
b3bb47c
remove assertion
Manfredss Jul 30, 2026
2f3cc36
fix fleet tests failure
Manfredss Jul 31, 2026
ec1295c
Merge branch 'develop' of https://github.com/paddlepaddle/paddle into…
Manfredss Jul 31, 2026
f514e71
staged
Manfredss Jul 31, 2026
a721161
Merge branch 'develop' of https://github.com/paddlepaddle/paddle into…
Manfredss Aug 3, 2026
c64978f
reframe use_compat_guard
Manfredss Aug 3, 2026
826269a
remove unused methods
Manfredss Aug 3, 2026
a119dbe
fix counter; add dispatch_property
Manfredss Aug 3, 2026
5a5b7b3
fix
Manfredss Aug 3, 2026
219307c
fix tests
Manfredss Aug 3, 2026
fde90cf
Merge branch 'develop' of https://github.com/paddlepaddle/paddle into…
Manfredss Aug 4, 2026
b0217d4
Merge branch 'develop' of https://github.com/paddlepaddle/paddle into…
Manfredss Aug 5, 2026
82f8ebf
fix
Manfredss Aug 6, 2026
ce136ec
Merge branch 'develop' of https://github.com/paddlepaddle/paddle into…
Manfredss Aug 6, 2026
3eeb401
improve code per review suggestions
Manfredss Aug 6, 2026
43c95f1
fix
Manfredss Aug 7, 2026
8c035b1
refine
Manfredss Aug 7, 2026
d72bb61
Merge branch 'develop' of https://github.com/paddlepaddle/paddle into…
Manfredss Aug 12, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
128 changes: 128 additions & 0 deletions python/paddle/compat/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
from collections.abc import Sequence

from paddle import Tensor
from paddle._typing import DTypeLike

__all__ = [
'allclose',
Expand All @@ -55,6 +56,26 @@
'seed',
]

_TENSOR_TYPE_NAMES = {
'float16': 'HalfTensor',
'float32': 'FloatTensor',
'float64': 'DoubleTensor',
'float8_e4m3fn': 'Float8_e4m3fnTensor',
'float8_e5m2': 'Float8_e5m2Tensor',
'bfloat16': 'BFloat16Tensor',
'uint8': 'ByteTensor',
'int8': 'CharTensor',
'int16': 'ShortTensor',
'int32': 'IntTensor',
'int64': 'LongTensor',
'bool': 'BoolTensor',
'complex64': 'ComplexFloatTensor',
'complex128': 'ComplexDoubleTensor',
}
_TENSOR_TYPE_DTYPES = {
tensor_type: dtype for dtype, tensor_type in _TENSOR_TYPE_NAMES.items()
}


def __getattr__(name):
if name == "paddle_triton":
Expand All @@ -66,6 +87,113 @@ def __getattr__(name):
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")


def _tensor_numel(input: Tensor) -> int:
"""
Returns the total number of elements in the tensor.

Args:
input (Tensor): The input tensor.

Returns:
int: The number of elements in ``input``.
"""
return int(input.size)


def _tensor_type(
input: Tensor,
dtype: DTypeLike | str | type | None = None,
non_blocking: bool = False,
**kwargs: Any,
) -> str | Tensor:
Comment thread
Manfredss marked this conversation as resolved.
"""
Returns the tensor dtype when ``dtype`` is not specified, otherwise casts
the tensor to the requested type.

Args:
input (Tensor): The input tensor.
dtype (DTypeLike|str|type|None, optional): The target tensor type or
data type. Qualified ``torch.*`` and ``paddle.*`` dtype or tensor
type strings are supported. When it is ``None``, returns a Paddle
dtype string. Default: ``None``.
non_blocking (bool, optional): Whether the conversion may occur
asynchronously. Default: ``False``.

Returns:
str|Tensor: A Paddle dtype string when ``dtype`` is ``None``;
otherwise, a tensor with the requested type.
"""
if "async" in kwargs:
non_blocking = kwargs.pop("async")
if kwargs:
key = next(iter(kwargs))
raise TypeError(f"type() got an unexpected keyword argument {key!r}")

if dtype is None:
return str(input.dtype)

@zhwesky2010 zhwesky2010 Aug 11, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

你这个地方需要映射为torch一致的paddle.FloatTensor这种格式

@Manfredss Manfredss Aug 11, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这个是 paddle.FloatTensor 还是 torch.FloatTensor? paconvert 那边比较的话是拿 paddle 的返回值和 torch 的返回值比对的,torch 返回 'torch.FloatTensor', 所以我觉得这里也用 torch 吧,但确实 paddle 里调用返回 torch 很怪

>>> import torch
>>> a = torch.tensor([2.], dtype=float32)
>>> a.type()
'torch.FloatTensor'

然后像 uint16 这些直接返回字符串的,返回的也是 paddle.uint16 这样的,需要拼成 torch 前缀吗

@Manfredss Manfredss Aug 11, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@zhwesky2010

还有这个问题 #79641 (comment)

我开了一个测试 PR 修这个问题,如果这样修改没问题的话我再推这个 PR 上

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

我的想法是另开一个 PR 修这个问题,要涉及到修改 creation.py,并且还有是 torch 还是 paddle 前缀的问题

@Manfredss Manfredss Aug 12, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

我的想法是另开一个 PR 修这个问题,要涉及到修改 creation.py,并且还有是 torch 还是 paddle 前缀的问题

我认为 #79641 这样的修改是对的

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

当前这项仍需在本 PR 修复。compat 模式下 t.type() 仍返回 paddle.float32,而本 PR 描述明确声明对齐 PyTorch 的 type query;CPU float32 应返回 torch.FloatTensor,GPU 与 sparse COO 还需编码 place/layout。现有测试反而将 paddle.float32 固化为预期值。请将 #79641 中已确认的类型名生成逻辑及 CPU/GPU/稀疏回归测试合入本 PR 后再合入;单独后续修复会使本 PR 先发布一个与声明不一致的公共行为。


device = None
if isinstance(dtype, type) and dtype.__name__ in _TENSOR_TYPE_DTYPES:

@zhwesky2010 zhwesky2010 Aug 5, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这里是否考虑了全部情况且能正确执行:

paddle.float32
np.float32
'float32'
'paddle.FloatTensor'
'torch.FloatTensor'

tensor_type = dtype.__name__
dtype = _TENSOR_TYPE_DTYPES[tensor_type]

@zhwesky2010 zhwesky2010 Aug 7, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

np.float32触发这个分支吗?看起来有点问题

直接判断np.dtype、paddle.dtype吧,鲁棒些。

这个isinstance(dtype, type) 比较奇怪

device = "cpu"
elif isinstance(dtype, str):
dtype_string = dtype
tensor_type = dtype_string.rsplit(".", 1)[-1]
if not dtype_string.startswith(("torch.", "paddle.")):
raise ValueError(f"invalid type: {dtype_string!r}")
if tensor_type in _TENSOR_TYPE_DTYPES:
dtype = _TENSOR_TYPE_DTYPES[tensor_type]
device = (
"gpu"
if dtype_string.startswith(("torch.cuda.", "paddle.cuda."))
else "cpu"
)
elif tensor_type in _TENSOR_TYPE_NAMES:
dtype = tensor_type
if dtype_string.startswith(("torch.cuda.", "paddle.cuda.")):

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这个是什么case?有 'torch.cuda.float32'

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

确实没有 我删一下

device = "gpu"
else:
raise ValueError(f"invalid type: {dtype_string!r}")

dtype_name = str(input.dtype).removeprefix("paddle.")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

直接判断:str(input.dtype) == dtype 就可以了吧

dtype本身就是字符串

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

dtype 走到这里不一定是字符串好像,像t.type(paddle.float32)t.type(np.float32)t.type(np.dtype('float32')) 这些都不进上面两个分支,会直接传下来,而且即使是字符串,前面也已经把 'torch.DoubleTensor'、'paddle.float64' 这些统一成了 'float64',和带前缀的 str(input.dtype)('paddle.float64')比的话是不一致的 我还是想保留现在的写法

target_dtype_name = str(dtype).removeprefix("paddle.")
same_device = (
device is None
or (device == "cpu" and input.place.is_cpu_place())
or (device == "gpu" and input.place.is_gpu_place())
)
if dtype_name == target_dtype_name and same_device:
return input

return input.to(
device=device,
dtype=dtype,
blocking=not non_blocking,
)


@property
def _tensor_is_sparse(input: Tensor) -> bool:
Comment thread
Manfredss marked this conversation as resolved.
Comment thread
Manfredss marked this conversation as resolved.
"""
Whether the tensor uses the sparse COO layout.

Args:
input (Tensor): The input tensor.

Returns:
bool: ``True`` for a sparse COO tensor, otherwise ``False``.
"""
return input.is_sparse_coo()


_TENSOR_API_OVERRIDES = {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

我建议这个是不是和上面的字典合并起来,统一成一个字典:

_TENSOR_API_OVERRIDES = (
    'allclose': allclose,
    'equal': equal
    'numel': _tensor_numel,
)

'numel': _tensor_numel,
'type': _tensor_type,
'is_sparse': _tensor_is_sparse,
}


def allclose(
input: Tensor,
other: Tensor,
Expand Down
88 changes: 64 additions & 24 deletions python/paddle/compat/api_dispatch.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,10 @@
# 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.
"""``enable_compat(level=2)`` and ``enable_compat(level=3)`` 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.
Expand Down Expand Up @@ -54,9 +55,10 @@ def _caller_is_paddle_internal() -> bool:
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."""
disabled state get the native callable. Installed under
``enable_compat(level=2)`` or ``enable_compat(level=3)``;
``disable_compat`` restores the originals, so the default hot path is
untouched."""

def decorator(native_fn: Any) -> Any:
@wraps(native_fn)
Expand All @@ -77,11 +79,12 @@ def dispatcher(*args: Any, **kwargs: Any) -> Any:


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.
"""
"""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 under
``enable_compat(level=2)`` or ``enable_compat(level=3)``;
``disable_compat`` restores the originals, so the default hot path is
untouched."""
import paddle.compat

if hasattr(paddle.compat, "__all__"):
Expand Down Expand Up @@ -123,27 +126,64 @@ def __call__(cls, *args: Any, **kwargs: Any) -> Any:
return proxy


def dispatch_property(compat_attr: Any) -> Any:
"""Route a Tensor API when either side uses the property protocol."""

def decorator(native_attr: Any) -> Any:
compat_is_property = isinstance(compat_attr, property)
compat_fn = compat_attr.fget if compat_is_property else compat_attr

class _PropertyDispatcher:
def __get__(self, instance: Any, owner: type | None = None) -> Any:
if instance is None:
if _caller_is_paddle_internal():
return native_attr
return self if compat_is_property else compat_fn
if (
len(_PADDLE_NAMESPACE_SAVED) > 0
and not _caller_is_paddle_internal()
):
if compat_is_property:
return compat_fn(instance)
return compat_fn.__get__(instance, owner)
return native_attr.__get__(instance, owner)

dispatcher = _PropertyDispatcher()
dispatcher.__native_fn__ = native_attr
dispatcher.__compat_fn__ = compat_fn
dispatcher.__doc__ = compat_fn.__doc__
dispatcher.__name__ = compat_fn.__name__
dispatcher.__signature__ = inspect.signature(compat_fn)
return dispatcher

return decorator


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.
"""
"""Route ``paddle.Tensor`` APIs to their root compat implementations."""
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:
tensor_apis = {
attr_name: getattr(compat_root, attr_name)
for attr_name in getattr(compat_root, "__all__", ())

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

paddle.compat.__all__ 里的所有函数都是 tensor 上的 API 么?总感觉不太对?

虽然下面有过滤,但是这里的命名 -> method 的对应关系总感觉不应该按照这个假设?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

呃 确实不是全部 __all__ 里的 seed 就不是 Tensor API,它靠 inspect.getattr_static(paddle.Tensor, name, None) is None 过滤掉;其余 10 个在 Tensor 上同名同义、首参对应描述符传入的 tensor,能对上

我改一下 更鲁棒一点

}
tensor_apis.update(compat_root._TENSOR_API_OVERRIDES)
for attr_name, compat_attr in tensor_apis.items():
native_attr = inspect.getattr_static(paddle.Tensor, attr_name, None)
if native_attr is None:
continue
compat_fn = getattr(compat_root, attr_name)
_PADDLE_NAMESPACE_SAVED[(paddle.Tensor, attr_name)] = native_method
_PADDLE_NAMESPACE_SAVED[(paddle.Tensor, attr_name)] = native_attr
if isinstance(compat_attr, property) or inspect.isdatadescriptor(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

在这个地方做四分支判断,最大化提升性能

native_attr
):
dispatcher = dispatch_property
else:
dispatcher = dispatch_function
setattr(
paddle.Tensor,
attr_name,
dispatch_function(compat_fn)(native_method),
dispatcher(compat_attr)(native_attr),
)


Expand Down
16 changes: 16 additions & 0 deletions python/paddle/compat/distributions/categorical.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@

from ..utils import _CompatClassMeta

__all__ = ["Categorical"]


class Categorical(distribution.Distribution, metaclass=_CompatClassMeta):
arg_constraints = {
Expand Down Expand Up @@ -66,6 +68,20 @@ def __init__(
distribution.Distribution.__init__(
self, batch_shape, validate_args=validate_args
)
if self._validate_args_enabled and paddle.in_dynamic_mode():
if probs is not None:
param_name = "probs"
valid = paddle.all(self.probs >= 0, axis=-1) & (
(self.probs.sum(-1) - 1).abs() < 1e-6
)
else:
param_name = "logits"
valid = constraint.real_vector.check(self.logits)
if not bool(valid.all()):
raise ValueError(
f'Expected parameter {param_name} of distribution '
'Categorical to satisfy its constraint'
)
Comment thread
Manfredss marked this conversation as resolved.

def expand(self, batch_shape, _instance=None):
new = (
Expand Down
Loading
Loading