Skip to content
Open
Show file tree
Hide file tree
Changes from 12 commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
321f32b
change for enable_compat
Manfredss Jun 29, 2026
9bf7bfb
add two left api
Manfredss Jun 29, 2026
204c7a1
Merge branch 'master' of https://github.com/paddlepaddle/paconvert in…
Manfredss Jul 3, 2026
edb4042
[API Compatibility] inject enable_compat(level=2) so prefix-converted…
Manfredss Jul 3, 2026
1ff4284
Merge branch 'master' of https://github.com/paddlepaddle/paconvert in…
Manfredss Jul 7, 2026
020ce44
Merge branch 'master' of https://github.com/paddlepaddle/paconvert in…
Manfredss Jul 7, 2026
e584efa
[API Compatibility] refine enable_compat injection; activate compat r…
Manfredss Jul 7, 2026
bfff52a
Merge branch 'patch_compat' of https://github.com/manfredss/paconvert…
Manfredss Jul 7, 2026
3dcd2a1
update ChangePrefixMatcher for those can passed under enable_compat(l…
Manfredss Jul 8, 2026
81a3c07
ChangePrefixMatcher for BatchNorm1d/2d/3d
Manfredss Jul 8, 2026
8170d4f
[API Compatibility] fix softmin/allclose/sort matchers under enable_c…
Manfredss Jul 9, 2026
f04bbf3
fix code conversion mis-alignment
Manfredss Jul 9, 2026
9bef7f9
Merge upstream master into patch_compat
Manfredss Jul 14, 2026
838a8a5
restore test script
Manfredss Jul 14, 2026
f7e1c04
update min-mode consistency baselines
Manfredss Jul 14, 2026
5fe7de1
fix default-mode consistency: dedupe injected imports, update baselines
Manfredss Jul 14, 2026
14d8aeb
Simplify ImportTransformer.visit_Module to insert paddle imports and …
Manfredss Jul 15, 2026
e0808c4
Merge branch 'patch_compat' of https://github.com/manfredss/paconvert…
Manfredss Jul 15, 2026
6bd002d
paddle_package == paddle and add once
Manfredss Jul 16, 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
220 changes: 34 additions & 186 deletions paconvert/api_mapping.json

Large diffs are not rendered by default.

94 changes: 94 additions & 0 deletions paconvert/transformer/import_transformer.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@ def __init__(
self.imports_map[self.file]["api_alias_name_map"] = {}
self.insert_pass_node = set()
self.change_prefix_api_map = defaultdict(set)
# Set in visit_Module: True when this file actually imports torch (so we
# added paddle imports) and therefore needs paddle.enable_compat injected.
self.need_enable_compat = False

def visit_Import(self, node):
"""
Expand Down Expand Up @@ -525,3 +528,94 @@ def visit_Module(self, node):
(self.root, "body", 0), ast.parse(f"import {paddle_package}").body
)
line_NO += 1

# enable_compat is injected in transform() (needs all imports in the body).
# Gate on real torch imports, not paddle_package_list: the latter also holds
# MAY_TORCH packages (os/einops/setuptools) that need no compat switch.
if self.imports_map[self.file]["torch_packages"]:

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

简化下代码,看有无更简单的写法,是否只需要修改visit_Module就可以?

self.need_enable_compat = True

def transform(self):
super(ImportTransformer, self).transform()
self._inject_enable_compat()

@staticmethod
def _is_future_import(node):
return isinstance(node, ast.ImportFrom) and node.module == "__future__"

@staticmethod
def _is_import(node):
return isinstance(node, (ast.Import, ast.ImportFrom))

@staticmethod
def _is_docstring(node):
return (
isinstance(node, ast.Expr)
and isinstance(node.value, ast.Constant)
and isinstance(node.value.value, str)
)

@staticmethod
def _is_enable_compat_call(node):
return (
isinstance(node, ast.Expr)
and isinstance(node.value, ast.Call)
and isinstance(node.value.func, ast.Attribute)
and node.value.func.attr == "enable_compat"
and isinstance(node.value.func.value, ast.Name)
and node.value.func.value.id == "paddle"
)

@staticmethod
def _binds_paddle(node):
# `import paddle` or `import paddle.xxx` (no asname) binds the name `paddle`
if isinstance(node, ast.Import):
for alias_node in node.names:
if alias_node.asname is None and (
alias_node.name == "paddle" or alias_node.name.startswith("paddle.")
):
return True
return False

def _inject_enable_compat(self):

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

这个使用record_scope不能插入吗?插入这个不需要从0开始重写吧

"""Insert ``paddle.enable_compat(level=2)`` after the docstring,
``__future__`` imports and the import block (level=2 aliases the
torch-aligned ``paddle.compat.*`` APIs onto ``paddle.*``). Post-pass so it
is never placed above a ``__future__`` import (which is a SyntaxError).
"""
if not self.need_enable_compat:
return

body = [n for n in self.root.body if not self._is_enable_compat_call(n)]

# hoist all __future__ imports (they must precede every other statement)
futures = [n for n in body if self._is_future_import(n)]
body = [n for n in body if not self._is_future_import(n)]

# hoist the module docstring if only imports precede it (we prepend imports)
doc = []
for i, node in enumerate(body):
if self._is_docstring(node) and all(self._is_import(n) for n in body[:i]):
doc = [node]
body = body[:i] + body[i + 1 :]
break

# split off the contiguous import block at the top of the remainder
end = 0
while end < len(body) and self._is_import(body[end]):
end += 1
imports, rest = body[:end], body[end:]

# enable_compat needs the name `paddle` bound (submodule-only aliases may
# not bind it, e.g. `import torch.nn as nn` -> `import paddle.nn as nn`)
if not any(self._binds_paddle(n) for n in imports):
imports = ast.parse("import paddle").body + imports

compat = ast.parse("paddle.enable_compat(level=2)").body
self.root.body = doc + futures + imports + compat + rest
ast.fix_missing_locations(self.root)
log_info(
self.logger,
"add 'paddle.enable_compat(level=2)' after imports",
self.file_name,
)
5 changes: 5 additions & 0 deletions tests/apibase.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@
import numpy as np

sys.path.append(os.path.dirname(__file__) + "/..")
sys.path.append(os.path.dirname(__file__))

from conftest import disable_paddle_compat

from paconvert.converter import Converter

Expand Down Expand Up @@ -85,6 +88,7 @@ def run(
)
assert paddle_code == expect_paddle_code, error_msg
elif compared_tensor_names:
disable_paddle_compat()
pytorch_ns = {}
try:
exec(pytorch_code, pytorch_ns)
Expand Down Expand Up @@ -117,6 +121,7 @@ def run(
except Exception as e:
raise AssertionError(f"Unable to align results: {e}")
else:
disable_paddle_compat()
pytorch_ns = {}
try:
exec(pytorch_code, pytorch_ns)
Expand Down
50 changes: 50 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# 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.

import sys

import pytest


def disable_paddle_compat():
"""Turn OFF Paddle's torch-compat proxy if it is currently active.

Converted Paddle code injects ``paddle.enable_compat(level=2)``, which flips
process-global state: it installs an ``import torch`` -> Paddle proxy and
aliases ``paddle.*`` to the torch-aligned ``paddle.compat.*`` APIs. That state
must be cleared so it cannot leak into (a) a later test, or (b) a torch
*reference* run within the same test, whose ``import torch`` would otherwise be
proxied to Paddle so the reference would no longer be real torch.

Lazy and best-effort: a no-op when Paddle was never imported, so it does not
force a Paddle import (and thus does not change torch/paddle import ordering)
for tests that never touch Paddle.
"""
if "paddle" not in sys.modules:
return
paddle = sys.modules["paddle"]
try:
from paddle.compat.proxy import TORCH_PROXY_FINDER

while TORCH_PROXY_FINDER in sys.meta_path:
paddle.disable_compat()
except Exception:
pass


@pytest.fixture(autouse=True)
def _reset_paddle_compat_mode():
"""Disable torch-compat after every test so it cannot leak into a later one."""
yield
disable_paddle_compat()
48 changes: 25 additions & 23 deletions tests/test_Tensor_median.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,12 +38,7 @@ def test_case_2():
result = input.median(1)
"""
)
obj.run(
pytorch_code,
["result"],
unsupport=True,
reason="paddle does not return index when dim is specified",
)
obj.run(pytorch_code, ["result"])


def test_case_3():
Expand All @@ -54,12 +49,7 @@ def test_case_3():
result = input.median(1, keepdim=True)
"""
)
obj.run(
pytorch_code,
["result"],
unsupport=True,
reason="paddle does not return index when dim is specified",
)
obj.run(pytorch_code, ["result"])


def test_case_4():
Expand All @@ -70,12 +60,7 @@ def test_case_4():
result = input.median(dim=1, keepdim=True)
"""
)
obj.run(
pytorch_code,
["result"],
unsupport=True,
reason="paddle does not return index when dim is specified",
)
obj.run(pytorch_code, ["result"])


def test_case_5():
Expand All @@ -86,9 +71,26 @@ def test_case_5():
result = input.median(0)
"""
)
obj.run(
pytorch_code,
["result"],
unsupport=True,
reason="paddle does not return index when dim is specified",
obj.run(pytorch_code, ["result"])


def test_case_6():
pytorch_code = textwrap.dedent(
"""
import torch
input = torch.tensor([1.0, 2.0, 3.0, 4.0])
result = input.median()
"""
)
obj.run(pytorch_code, ["result"])


def test_case_7():
pytorch_code = textwrap.dedent(
"""
import torch
input = torch.tensor([[1.0, 2.0, 3.0, 4.0], [8.0, 7.0, 6.0, 5.0]])
result = input.median(dim=1)
"""
)
obj.run(pytorch_code, ["result"])
8 changes: 7 additions & 1 deletion tests/test_Tensor_rename.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,13 @@ def test_case_1():
)
obj.run(
pytorch_code,
expect_paddle_code='import paddle\n\nx = paddle.tensor([1, 2, 3])\nx.rename(columns={"iids": iids})\n',
expect_paddle_code="""
import paddle

paddle.enable_compat(level=2)
x = paddle.tensor([1, 2, 3])
x.rename(columns={"iids": iids})
""",
)


Expand Down
1 change: 1 addition & 0 deletions tests/test_Tensor_split.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ def test_case_7():
"""
import paddle

paddle.enable_compat(level=2)
a = paddle.tensor([1, 2, 3])
str1 = "1,2,3"
str1.split(",")
Expand Down
2 changes: 2 additions & 0 deletions tests/test_add_start_docstrings_to_model_forward.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ def forward(self, input_ids):
import paddle
import paddleformers

paddle.enable_compat(level=2)


class LlamaForCausalLM(paddle.nn.Module):
@paddleformers.trainer.utils.add_start_docstrings_to_model_forward("test docstring")
Expand Down
2 changes: 2 additions & 0 deletions tests/test_jit_ignore.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ def helper_function(self, x):
"""
import paddle

paddle.enable_compat(level=2)


class MyModule(paddle.nn.Module):
def forward(self, x):
Expand Down
2 changes: 2 additions & 0 deletions tests/test_jit_load.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ def test_case_1():
"""
import paddle

paddle.enable_compat(level=2)
result = paddle.jit.load(path="model.pt")
"""
)
Expand All @@ -48,6 +49,7 @@ def test_case_2():
"""
import paddle

paddle.enable_compat(level=2)
result = paddle.jit.load(path="model.pt")
"""
)
Expand Down
8 changes: 8 additions & 0 deletions tests/test_jit_save.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ def forward(self, x):
"""
import paddle

paddle.enable_compat(level=2)


class MyModule(paddle.nn.Module):
def __init__(self):
Expand Down Expand Up @@ -89,6 +91,8 @@ def forward(self, x):
"""
import paddle

paddle.enable_compat(level=2)


class MyModule(paddle.nn.Module):
def __init__(self):
Expand Down Expand Up @@ -134,6 +138,8 @@ def forward(self, x):
"""
import paddle

paddle.enable_compat(level=2)


class MyModule(paddle.nn.Module):
def __init__(self):
Expand Down Expand Up @@ -180,6 +186,8 @@ def forward(self, x):
"""
import paddle

paddle.enable_compat(level=2)


class MyModule(paddle.nn.Module):
def __init__(self):
Expand Down
6 changes: 6 additions & 0 deletions tests/test_jit_script.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ def foo(x, scale, shift):
"""
import paddle

paddle.enable_compat(level=2)


@paddle.jit.to_static
def foo(x, scale, shift):
Expand Down Expand Up @@ -66,6 +68,8 @@ def add(x, y):
"""
import paddle

paddle.enable_compat(level=2)


def add(x, y):
return x + y
Expand Down Expand Up @@ -99,6 +103,8 @@ def add(x, y):
"""
import paddle

paddle.enable_compat(level=2)


def add(x, y):
return x + y
Expand Down
6 changes: 4 additions & 2 deletions tests/test_onnx_export.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,12 +105,14 @@ def onnx_export(model,f):
############################## 相关utils函数,如上 ##############################


paddle.enable_compat(level=2)


class SimpleModel(paddle.nn.Module):
def __init__(self):
super(SimpleModel, self).__init__()
self.fc1 = paddle.compat.nn.Linear(3, 3)
self.fc2 = paddle.compat.nn.Linear(3, 1)
self.fc1 = paddle.nn.Linear(3, 3)
self.fc2 = paddle.nn.Linear(3, 1)

def forward(self, x):
x = paddle.relu(self.fc1(x))
Expand Down
Loading