Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
28 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
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
91 changes: 91 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,76 @@ def __getattr__(name):
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")


def _tensor_numel(input: Tensor) -> int:
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.
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:
dtype_name = str(input.dtype).removeprefix("paddle.")
tensor_type = _TENSOR_TYPE_NAMES[dtype_name]
prefix = "torch.cuda" if input.place.is_gpu_place() else "torch"

@zhwesky2010 zhwesky2010 Aug 2, 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.*

if input.is_sparse_coo():
prefix += ".sparse"
return f"{prefix}.{tensor_type}"

device = None
if isinstance(dtype, type):

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.

P2 优先级:P2

问题: 这里用 isinstance(dtype, type) 捕获所有 Python type,然后只按 FloatTensor/DoubleTensor 这类 tensor factory 名称识别。DTypeLike 里包含 np.float32np.float64np.int64 等 numpy scalar type;这些对象也是 type,会在这里因为 dtype.__name__(例如 "float64")不在 _TENSOR_TYPE_DTYPES 而直接 ValueError,无法走后面的 input.to(dtype=dtype)

影响: enable_compat(level=2) 后,t.type(np.float64) 这类 Paddle 其他 dtype 参数常见写法会被误判为非法 tensor class。与当前签名里的 DTypeLike | str | type 不一致,也缺少测试覆盖。

处理要求:请针对该评论进行回复(同意并已修改请回复 Done,不同意请说明理由)。

期望: 只把已知 tensor factory class 当作 PyTorch-style class conversion,其他 DTypeLike 继续交给 Tensor.to 校验/转换,并补一个 np.float64 之类的回归测试。可以先把该行收窄为:

Suggested change
if isinstance(dtype, type):
if isinstance(dtype, type) and dtype.__name__ in _TENSOR_TYPE_DTYPES:

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.

P2 优先级:P2

补充复查:新提交的 test_tensor_type_edge_cases 覆盖了 t.type(paddle.float64, **{"async": True}),但这个参数不是 Python type,不会进入当前 if isinstance(dtype, type): 分支;原问题里的 np.float64 / np.int64 等 numpy scalar type 仍会进入该分支并因为 "float64" 不在 _TENSOR_TYPE_DTYPES 里抛 ValueError

处理要求:请针对该评论进行回复(同意并已修改请回复 Done,不同意请说明理由)。

建议仍按上面 suggestion 收窄 tensor factory class 的判断,并把回归测试补到 numpy dtype 路径,例如:

import numpy as np

self.assertEqual(t.type(np.float64).dtype, paddle.float64)

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.

P1 优先级:P1

补充复查:这个提交把原来的判断改成了 dtype.__name__ in _TENSOR_TYPE_NAMES,但 _TENSOR_TYPE_NAMES 的 key 是 "float32"/"float64" 这类 dtype 名;paddle.DoubleTensor 来自 dtype_tensor_factory('float64', 'DoubleTensor')__name__"DoubleTensor"。因此 t.type(paddle.DoubleTensor) 不会进入这里的 tensor factory class 分支,会继续把 class 对象传给 input.to(dtype=...),这不是 DTypeLike 支持的输入,新增的 self.assertEqual(t.type(paddle.DoubleTensor).dtype, paddle.float64) 也会失败。

处理要求:请针对该评论修复并提交新的 commit。

请把 class 分支的 membership 查 _TENSOR_TYPE_DTYPES(反查表)而不是 _TENSOR_TYPE_NAMES,并保留这次补上的 np.float64 回归测试。修复形态可以是:

if isinstance(dtype, type) and dtype.__name__ in _TENSOR_TYPE_DTYPES:
    tensor_type = dtype.__name__
    dtype = _TENSOR_TYPE_DTYPES[tensor_type]
    device = "cpu"

tensor_type = dtype.__name__
if tensor_type not in _TENSOR_TYPE_DTYPES:
raise ValueError(f"invalid type: {tensor_type!r}")
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 (

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.xxx,也可以是paddle.xxx

not dtype_string.startswith("torch.")
or tensor_type not in _TENSOR_TYPE_DTYPES
):
raise ValueError(f"invalid type: {dtype_string!r}")
dtype = _TENSOR_TYPE_DTYPES[tensor_type]
device = "gpu" if dtype_string.startswith("torch.cuda.") else "cpu"

@zhwesky2010 zhwesky2010 Aug 2, 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 API进行兼容性设计:输入应同时支持torch.xxx和paddle.xxx,输出为paddle.xxx

这里好几个地方思路都不对


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,
)


def _tensor_is_sparse(input: Tensor) -> bool:
Comment thread
Manfredss marked this conversation as resolved.
Comment thread
Manfredss marked this conversation as resolved.
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, False),
'type': (_tensor_type, False),
'is_sparse': (_tensor_is_sparse, True),
}


def allclose(
input: Tensor,
other: Tensor,
Expand Down
45 changes: 45 additions & 0 deletions python/paddle/compat/api_dispatch.py
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,37 @@ def __call__(cls, *args: Any, **kwargs: Any) -> Any:
return proxy


class _TensorCompatDescriptor:

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 method这里为何会有这么多特殊之处?除了名字不同。

这一块的设计比较冗余,优化下设计,与其他tensor method合并处理。尽可能代码复用并减少行数。

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.

不太理解要加这个东西解决什么问题,如果只是一个property的问题,单独整一个 dispatch_property 这个函数,其他继续使用dispatch_function

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.

这个问题点改了吗?上次沟通的意思理解了没?看起来和沟通的不是一回事

disptch_function + disptch_property

"""Caller-aware adapter for Tensor method/property shape differences."""

def __init__(
self,
native_attr: Any,
compat_fn: Any,
as_property: bool,
) -> None:
self.__native_fn__ = native_attr
self.__compat_fn__ = compat_fn
self._as_property = as_property
self.__doc__ = compat_fn.__doc__
self.__name__ = compat_fn.__name__
self.__signature__ = inspect.signature(compat_fn)

def __get__(self, instance: Any, owner: type | None = None) -> Any:
if instance is None:
if _caller_is_paddle_internal():
return self.__native_fn__
return self if self._as_property else self.__compat_fn__
if (
len(_PADDLE_NAMESPACE_SAVED) > 0
and not _caller_is_paddle_internal()
):
if self._as_property:
return self.__compat_fn__(instance)
return self.__compat_fn__.__get__(instance, owner)
return self.__native_fn__.__get__(instance, owner)


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
Expand All @@ -146,6 +177,20 @@ def _patch_tensor_methods() -> None:
dispatch_function(compat_fn)(native_method),
)

for attr_name, (

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 和 __all__先合并为字典,然后再统一进行patch

compat_fn,
as_property,
) in compat_root._TENSOR_API_OVERRIDES.items():
native_attr = inspect.getattr_static(paddle.Tensor, attr_name, None)
if native_attr is None:
continue
_PADDLE_NAMESPACE_SAVED[(paddle.Tensor, attr_name)] = native_attr
setattr(
paddle.Tensor,
attr_name,
_TensorCompatDescriptor(native_attr, compat_fn, as_property),
)


def _apply_paddle_namespace_aliases() -> None:
"""Install caller-aware dispatchers/proxies for every public ``paddle.compat.*``
Expand Down
11 changes: 11 additions & 0 deletions python/paddle/nn/layer/activation.py
Original file line number Diff line number Diff line change
Expand Up @@ -586,6 +586,17 @@ def __init__(
device: PlaceLike | None = None,
dtype: DTypeLike | None = None,
) -> None:
is_torch_device = weight_attr is None or (
isinstance(weight_attr, str)
and weight_attr.split(':', 1)[0].lower()
in {'cpu', 'cuda', 'gpu', 'xpu', 'mps', 'meta'}
)
if is_torch_device:
if not isinstance(data_format, str):
device, dtype = weight_attr, data_format
weight_attr, data_format = None, "NCHW"
elif weight_attr is not None and data_format == "NCHW":
device, weight_attr = weight_attr, None

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.

P1 优先级:P1

问题: 这里把第三个位置参数为 "cpu"/"cuda" 等字符串且 data_format 仍为默认值的调用重解释成 PyTorch 的 device。但 weight_attr: ParamAttrLike 旧 API 明确支持字符串参数名,ParamAttr._to_attr("cpu") 会创建名为 "cpu" 的参数;现在 paddle.nn.PReLU(2, 0.5, "cpu") 会丢掉 weight_attr 并创建匿名参数。

影响: 这是原生 paddle.nn.PReLU 构造函数的全局行为变化,不需要开启 enable_compat(level=2) 就会触发,会破坏依赖参数名的旧模型代码、静态图或 checkpoint/state_dict 兼容性。

处理要求:请针对该评论修复并提交新的 commit。

期望: 保留旧的字符串 weight_attr 语义;PyTorch 的无 dtype 位置参数 device 形态建议放到 paddle.compat.nn.PReLU/level=2 alias 中处理,或至少只在不会与旧 weight_attr 字符串冲突的形态下重解释。例如可以按下面的形态收窄 native 构造函数里的自动重解释:

# 伪代码:仅处理第四个位置参数明确是 dtype/device pair 的无冲突形态
if not isinstance(data_format, str):
    device, dtype = weight_attr, data_format
    weight_attr, data_format = None, "NCHW"

# PReLU(..., "cpu") 这种无 dtype 的 PyTorch 形态不要在 native paddle.nn.PReLU
# 中覆盖字符串 weight_attr;如需支持,请放到 compat-only wrapper 中。

super().__init__()
self._num_parameters = num_parameters
self._init = init
Expand Down
57 changes: 56 additions & 1 deletion test/compat/test_compat_namespace_aliased.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,9 @@ class CompatNamespaceAliasBase(unittest.TestCase):
(paddle, "allclose"),
(paddle, "equal"),
(paddle, "seed"),
(paddle.Tensor, "numel"),
(paddle.Tensor, "type"),
(paddle.Tensor, "is_sparse"),
(paddle.nn, "AvgPool1d"),
(paddle.nn, "AvgPool2d"),
(paddle.nn, "AvgPool3d"),
Expand Down Expand Up @@ -152,6 +155,16 @@ def test_level1_default_does_not_alias(self):
paddle.enable_compat() # default level=1
try:
self.assertIs(paddle.sort, self._native[(paddle, "sort")])
self.assertIs(
paddle.Tensor.numel, self._native[(paddle.Tensor, "numel")]
)
self.assertIs(
paddle.Tensor.type, self._native[(paddle.Tensor, "type")]
)
self.assertIs(
paddle.Tensor.is_sparse,
self._native[(paddle.Tensor, "is_sparse")],
)
self.assertFalse(hasattr(paddle.sort, "__compat_fn__"))
with self.assertRaises(TypeError):
paddle.sort(
Expand Down Expand Up @@ -539,6 +552,9 @@ def test_tensor_methods_caller_aware(self):
for paddle-internal ``x.max(axis=1)``; restored on disable."""
native_max = paddle.Tensor.max
native_split = paddle.Tensor.split
native_numel = paddle.Tensor.numel
native_type = paddle.Tensor.type
native_is_sparse = paddle.Tensor.is_sparse
t = paddle.to_tensor([[3.0, 1.0, 2.0], [6.0, 5.0, 4.0]])
with level2_guard():
r = t.max(dim=1) # external -> compat namedtuple
Expand All @@ -551,15 +567,54 @@ def test_tensor_methods_caller_aware(self):
2,
)
self.assertEqual(len(paddle.split(t, split_size=1, dim=0)), 2)
self.assertEqual(t.numel(), 6)
self.assertIs(type(t.numel()), int)
self.assertIsInstance(paddle.numel(t), paddle.Tensor)
self.assertEqual(paddle.empty([0, 3]).numel(), 0)
self.assertEqual(t.type(), "torch.FloatTensor")
self.assertEqual(t.type(paddle.float64).dtype, paddle.float64)
self.assertEqual(t.type(paddle.DoubleTensor).dtype, paddle.float64)
self.assertEqual(t.type("torch.DoubleTensor").dtype, paddle.float64)
self.assertIs(t.type(paddle.float32), t)
self.assertIs(t.type("torch.FloatTensor"), t)
with self.assertRaises(ValueError):
t.type("float64")
self.assertEqual(
paddle.ones([1], dtype="int64").type(), "torch.LongTensor"
)
self.assertEqual(
paddle.ones([1], dtype="float8_e4m3fn").type(),
"torch.Float8_e4m3fnTensor",
)
self.assertEqual(
paddle.ones([1], dtype="float8_e5m2").type(),
"torch.Float8_e5m2Tensor",
)
self.assertIs(t.is_sparse, False)
coo = paddle.sparse.sparse_coo_tensor([[0], [1]], [1.0], [2, 2])
csr = paddle.sparse.sparse_csr_tensor([0, 1, 1], [0], [1.0], [2, 2])
self.assertIs(coo.is_sparse, True)
self.assertIs(csr.is_sparse, False)
# paddle-internal native-style call (simulated) stays native
ns = {"__name__": "paddle.fake_internal", "t": t}
exec(
"internal_max = t.max(axis=1)\n"
"internal_split = t.split(num_or_sections=2, axis=0)",
"internal_split = t.split(num_or_sections=2, axis=0)\n"
"internal_numel = t.numel()\n"
"internal_type = t.type\n"
"internal_is_sparse = t.is_sparse()",
ns,
)
self.assertIsInstance(ns["internal_numel"], paddle.Tensor)
self.assertEqual(
ns["internal_type"], native_type.__get__(t, paddle.Tensor)
)
self.assertIs(ns["internal_is_sparse"], False)
self.assertIs(paddle.Tensor.max, native_max) # restored on disable
self.assertIs(paddle.Tensor.split, native_split)
self.assertIs(paddle.Tensor.numel, native_numel)
self.assertIs(paddle.Tensor.type, native_type)
self.assertIs(paddle.Tensor.is_sparse, native_is_sparse)

@with_level2
def test_aliased_class_caller_aware(self):
Expand Down
23 changes: 19 additions & 4 deletions test/legacy_test/test_api_compatibility_part2.py
Original file line number Diff line number Diff line change
Expand Up @@ -3176,17 +3176,30 @@ def test_dygraph_Compatibility(self):
)(input=x)
# 4. Mixed arguments
out4 = paddle.nn.PReLU(2, init=0.5, device="cpu", dtype="float32")(x)
# 5. PyTorch positional arguments
out5 = paddle.nn.PReLU(2, 0.5, "cpu", paddle.float32)(x)
# 6. PyTorch positional device without dtype
out6 = paddle.nn.PReLU(2, 0.5, "cpu")(x)
# 7. PyTorch positional dtype without device
out7 = paddle.nn.PReLU(2, 0.5, None, paddle.float32)(x)

expected = self._expected(self.np_x)
for out in [out1, out2, out3, out4]:
for out in [out1, out2, out3, out4, out5, out6, out7]:
np.testing.assert_allclose(out.numpy(), expected, rtol=1e-6)

x64 = paddle.to_tensor(self.np_x64)
layer64 = paddle.nn.PReLU(2, 0.5, device="cpu", dtype="float64")
out5 = layer64(input=x64)
out8 = layer64(input=x64)
self.assertEqual(layer64._weight.dtype, paddle.float64)
np.testing.assert_allclose(
out5.numpy(), self._expected(self.np_x64), rtol=1e-6
out8.numpy(), self._expected(self.np_x64), rtol=1e-6
)

layer64_positional = paddle.nn.PReLU(2, 0.5, None, paddle.float64)
out9 = layer64_positional(x64)
self.assertEqual(layer64_positional._weight.dtype, paddle.float64)
np.testing.assert_allclose(
out9.numpy(), self._expected(self.np_x64), rtol=1e-6
)

paddle.enable_static()
Expand All @@ -3212,13 +3225,15 @@ def test_static_Compatibility(self):
out4 = paddle.nn.PReLU(2, init=0.5, device="cpu", dtype="float32")(
x
)
# 5. PyTorch positional arguments
out5 = paddle.nn.PReLU(2, 0.5, "cpu", paddle.float32)(x)

exe = paddle.static.Executor()
exe.run(startup)
fetches = exe.run(
main,
feed={"x": self.np_x},
fetch_list=[out1, out2, out3, out4],
fetch_list=[out1, out2, out3, out4, out5],
)

expected = self._expected(self.np_x)
Expand Down
Loading