From cd3c7f3f2d29b5b28d4131fd54fb04d668fd4eb0 Mon Sep 17 00:00:00 2001 From: manfredss Date: Fri, 24 Jul 2026 06:56:53 +0000 Subject: [PATCH 1/2] add json entry for torch.nn.modules.module._IncompatibleKeys and torch.optim.lr_scheduler.LRSchedule, add test file for torch.optim.lr_scheduler.LRSchedule --- paconvert/api_mapping.json | 6 ++ tests/test_optim_lr_scheduler_LRScheduler.py | 87 ++++++++++++++++++++ 2 files changed, 93 insertions(+) create mode 100644 tests/test_optim_lr_scheduler_LRScheduler.py diff --git a/paconvert/api_mapping.json b/paconvert/api_mapping.json index 135abe619..8e6d05af5 100644 --- a/paconvert/api_mapping.json +++ b/paconvert/api_mapping.json @@ -8192,6 +8192,9 @@ "torch.nn.modules.module.Module": { "Matcher": "ChangePrefixMatcher" }, + "torch.nn.modules.module._IncompatibleKeys": { + "Matcher": "ChangePrefixMatcher" + }, "torch.nn.modules.utils._ntuple": { "Matcher": "NTupleMatcher", "args_list": [ @@ -8773,6 +8776,9 @@ "torch.optim.lr_scheduler.ExponentialLR": { "Matcher": "ChangePrefixMatcher" }, + "torch.optim.lr_scheduler.LRScheduler": { + "Matcher": "ChangePrefixMatcher" + }, "torch.optim.lr_scheduler.LambdaLR": { "Matcher": "ChangePrefixMatcher" }, diff --git a/tests/test_optim_lr_scheduler_LRScheduler.py b/tests/test_optim_lr_scheduler_LRScheduler.py new file mode 100644 index 000000000..3d47b0c1d --- /dev/null +++ b/tests/test_optim_lr_scheduler_LRScheduler.py @@ -0,0 +1,87 @@ +# 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 textwrap + +from apibase import APIBase + +obj = APIBase("torch.optim.lr_scheduler.LRScheduler") + + +def generate_test_code(scheduler_init, prelude=""): + return f""" + import torch + + class ConstantScheduler(torch.optim.lr_scheduler.LRScheduler): + def get_lr(self): + if hasattr(self, "base_lrs"): + return [base_lr * 0.5 for base_lr in self.base_lrs] + return self.base_lr * 0.5 + + parameter = torch.nn.Parameter(torch.tensor([1.0])) + optimizer = torch.optim.SGD([parameter], lr=0.1) + {prelude} + scheduler = {scheduler_init} + loss = parameter.sum() + loss.backward() + optimizer.step() + result = parameter + result_epoch = scheduler.last_epoch + result_is_base = isinstance( + scheduler, torch.optim.lr_scheduler.LRScheduler + ) + """ + + +def run_test(scheduler_init, prelude=""): + pytorch_code = textwrap.dedent(generate_test_code(scheduler_init, prelude)) + obj.run(pytorch_code, ["result", "result_epoch", "result_is_base"]) + + +def test_case_1(): + """Optimizer positional argument with default last_epoch.""" + run_test("ConstantScheduler(optimizer)") + + +def test_case_2(): + """Optimizer keyword argument with default last_epoch.""" + run_test("ConstantScheduler(optimizer=optimizer)") + + +def test_case_3(): + """All arguments passed positionally.""" + run_test("ConstantScheduler(optimizer, -1)") + + +def test_case_4(): + """Mixed positional and keyword arguments.""" + run_test("ConstantScheduler(optimizer, last_epoch=-1)") + + +def test_case_5(): + """Keyword arguments passed out of order.""" + run_test("ConstantScheduler(last_epoch=-1, optimizer=optimizer)") + + +def test_case_6(): + """Variable positional arguments.""" + run_test("ConstantScheduler(*args)", "args = (optimizer,)") + + +def test_case_7(): + """Variable keyword arguments.""" + run_test( + "ConstantScheduler(**kwargs)", + 'kwargs = {"last_epoch": -1, "optimizer": optimizer}', + ) From dbf57d6f5c520c2adeb69ec248bdcb0c3408e776 Mon Sep 17 00:00:00 2001 From: manfredss Date: Fri, 24 Jul 2026 07:18:43 +0000 Subject: [PATCH 2/2] ChangePrefixMatcher for torch.nn.Module.register_forward_hook and torch.nn.Module.register_forward_pre_hook, enhance their tests --- paconvert/api_mapping.json | 30 +-- tests/test_nn_Module_register_forward_hook.py | 175 +++++++++++++++++- ...est_nn_Module_register_forward_pre_hook.py | 146 ++++++++++++++- 3 files changed, 312 insertions(+), 39 deletions(-) diff --git a/paconvert/api_mapping.json b/paconvert/api_mapping.json index 8e6d05af5..9484daa39 100644 --- a/paconvert/api_mapping.json +++ b/paconvert/api_mapping.json @@ -6772,36 +6772,10 @@ "Matcher": "ChangePrefixMatcher" }, "torch.nn.Module.register_forward_hook": { - "Matcher": "GenericMatcher", - "paddle_api": "paddle.nn.Module.register_forward_post_hook", - "args_list": [ - "hook", - "*", - "prepend", - "with_kwargs", - "always_call" - ], - "unsupport_args": [ - "prepend", - "with_kwargs", - "always_call" - ], - "min_input_args": 1 + "Matcher": "ChangePrefixMatcher" }, "torch.nn.Module.register_forward_pre_hook": { - "Matcher": "GenericMatcher", - "paddle_api": "paddle.nn.Module.register_forward_pre_hook", - "args_list": [ - "hook", - "*", - "prepend", - "with_kwargs" - ], - "unsupport_args": [ - "prepend", - "with_kwargs" - ], - "min_input_args": 1 + "Matcher": "ChangePrefixMatcher" }, "torch.nn.Module.register_full_backward_hook": { "min_input_args": 1 diff --git a/tests/test_nn_Module_register_forward_hook.py b/tests/test_nn_Module_register_forward_hook.py index fa4869e54..7c9d6e256 100644 --- a/tests/test_nn_Module_register_forward_hook.py +++ b/tests/test_nn_Module_register_forward_hook.py @@ -102,11 +102,7 @@ def hook(module, fea_in, fea_out): net(a) """ ) - obj.run( - pytorch_code, - unsupport=True, - reason="prepend, with_kwargs and always_call is not supported", - ) + obj.run(pytorch_code, ["result"]) def test_case_5(): @@ -132,8 +128,173 @@ def hook(module, fea_in, fea_out): net(a) """ ) + obj.run(pytorch_code, ["result"]) + + +def test_case_6(): + """prepend=True runs the new hook before existing hooks.""" + pytorch_code = textwrap.dedent( + """ + import torch + + events = [] + + class DoubleNum(torch.nn.Module): + def forward(self, x): + return 2 * x + + def first_hook(module, args, output): + events.append("first") + return output + 1 + + def second_hook(module, args, output): + events.append("second") + return output * 2 + + net = DoubleNum() + net.register_forward_hook(second_hook) + net.register_forward_hook(first_hook, prepend=True) + x = torch.tensor( + [[-1.5, 2.0], [0.25, -3.0]], + dtype=torch.float64, + ) + result = net(x) + """ + ) + obj.run(pytorch_code, ["events", "result"]) + + +def test_case_7(): + """with_kwargs=True passes forward keyword arguments to the hook.""" + pytorch_code = textwrap.dedent( + """ + import torch + + observed_kwargs = [] + + class ScaleAndShift(torch.nn.Module): + def forward(self, x, scale=1.0, offset=0.0): + return x * scale + offset + + def hook(module, args, kwargs, output): + observed_kwargs.append((kwargs["scale"], kwargs["offset"])) + return output + kwargs["scale"] + + net = ScaleAndShift() + net.register_forward_hook(hook, with_kwargs=True) + x = torch.tensor( + [[[-1.0, 0.5], [2.0, -3.0]]], + dtype=torch.float32, + ) + result = net(x, scale=2.5, offset=-1.0) + """ + ) + obj.run(pytorch_code, ["observed_kwargs", "result"]) + + +def test_case_8(): + """always_call=True invokes the hook when forward raises.""" + pytorch_code = textwrap.dedent( + """ + import torch + + events = [] + + class BrokenModule(torch.nn.Module): + def forward(self, x): + raise RuntimeError("forward failed") + + def hook(module, args, output): + events.append(output is None) + + net = BrokenModule() + net.register_forward_hook(hook, always_call=True) + try: + net(torch.tensor([1.0, -2.0])) + except RuntimeError: + pass + """ + ) + obj.run(pytorch_code, ["events"]) + + +def test_case_9(): + """The returned handle removes the registered hook.""" + pytorch_code = textwrap.dedent( + """ + import torch + + events = [] + + class AddOne(torch.nn.Module): + def forward(self, x): + return x + 1 + + def hook(module, args, output): + events.append("called") + return output * 2 + + net = AddOne() + handle = net.register_forward_hook(hook) + result_before_remove = net(torch.tensor([1, -2, 3])) + handle.remove() + result_after_remove = net(torch.tensor([1, -2, 3])) + """ + ) obj.run( pytorch_code, - unsupport=True, - reason="prepend, with_kwargs and always_call is not supported", + ["events", "result_before_remove", "result_after_remove"], + ) + + +def test_case_10(): + """The hook can be supplied through variable positional arguments.""" + pytorch_code = textwrap.dedent( + """ + import torch + + class DoubleNum(torch.nn.Module): + def forward(self, x): + return 2 * x + + def hook(module, args, output): + return output - 1 + + net = DoubleNum() + hook_args = (hook,) + net.register_forward_hook(*hook_args) + x = torch.tensor([[[-2.0, 1.0], [0.5, -0.25]]]) + result = net(x) + """ + ) + obj.run(pytorch_code, ["result"]) + + +def test_case_11(): + """All arguments can be supplied through variable keyword arguments.""" + pytorch_code = textwrap.dedent( + """ + import torch + + events = [] + + class DoubleNum(torch.nn.Module): + def forward(self, x): + return 2 * x + + def hook(module, args, kwargs, output): + events.append(len(kwargs)) + return output + 3 + + net = DoubleNum() + hook_kwargs = { + "hook": hook, + "prepend": False, + "with_kwargs": True, + "always_call": False, + } + net.register_forward_hook(**hook_kwargs) + result = net(torch.tensor([-1.0, 2.0])) + """ ) + obj.run(pytorch_code, ["events", "result"]) diff --git a/tests/test_nn_Module_register_forward_pre_hook.py b/tests/test_nn_Module_register_forward_pre_hook.py index c4a611541..4c4ddd2b4 100644 --- a/tests/test_nn_Module_register_forward_pre_hook.py +++ b/tests/test_nn_Module_register_forward_pre_hook.py @@ -102,9 +102,7 @@ def hook(module, fea_in): net(a) """ ) - obj.run( - pytorch_code, unsupport=True, reason="prepend and with_kwargs is not supported" - ) + obj.run(pytorch_code, ["result"]) def test_case_5(): @@ -130,6 +128,146 @@ def hook(module, fea_in): net(a) """ ) + obj.run(pytorch_code, ["result"]) + + +def test_case_6(): + """prepend=True runs the new pre-hook before existing pre-hooks.""" + pytorch_code = textwrap.dedent( + """ + import torch + + events = [] + + class DoubleNum(torch.nn.Module): + def forward(self, x): + return 2 * x + + def first_hook(module, args): + events.append("first") + return (args[0] + 1,) + + def second_hook(module, args): + events.append("second") + return (args[0] * 2,) + + net = DoubleNum() + net.register_forward_pre_hook(second_hook) + net.register_forward_pre_hook(first_hook, prepend=True) + x = torch.tensor( + [[-1.5, 2.0], [0.25, -3.0]], + dtype=torch.float64, + ) + result = net(x) + """ + ) + obj.run(pytorch_code, ["events", "result"]) + + +def test_case_7(): + """with_kwargs=True can modify positional and keyword inputs.""" + pytorch_code = textwrap.dedent( + """ + import torch + + observed_kwargs = [] + + class Scale(torch.nn.Module): + def forward(self, x, scale=1.0): + return x * scale + + def hook(module, args, kwargs): + observed_kwargs.append(kwargs["scale"]) + return (args[0] + 1,), {"scale": kwargs["scale"] + 1} + + net = Scale() + net.register_forward_pre_hook(hook, with_kwargs=True) + x = torch.tensor( + [[[-1.0, 0.5], [2.0, -3.0]]], + dtype=torch.float32, + ) + result = net(x, scale=2.5) + """ + ) + obj.run(pytorch_code, ["observed_kwargs", "result"]) + + +def test_case_8(): + """The returned handle removes the registered pre-hook.""" + pytorch_code = textwrap.dedent( + """ + import torch + + events = [] + + class AddOne(torch.nn.Module): + def forward(self, x): + return x + 1 + + def hook(module, args): + events.append("called") + return (args[0] * 3,) + + net = AddOne() + handle = net.register_forward_pre_hook(hook) + result_before_remove = net(torch.tensor([1, -2, 3])) + handle.remove() + result_after_remove = net(torch.tensor([1, -2, 3])) + """ + ) obj.run( - pytorch_code, unsupport=True, reason="prepend and with_kwargs is not supported" + pytorch_code, + ["events", "result_before_remove", "result_after_remove"], + ) + + +def test_case_9(): + """The hook can be supplied through variable positional arguments.""" + pytorch_code = textwrap.dedent( + """ + import torch + + class DoubleNum(torch.nn.Module): + def forward(self, x): + return 2 * x + + def hook(module, args): + return (args[0] - 1,) + + net = DoubleNum() + hook_args = (hook,) + net.register_forward_pre_hook(*hook_args) + x = torch.tensor([[[-2.0, 1.0], [0.5, -0.25]]]) + result = net(x) + """ + ) + obj.run(pytorch_code, ["result"]) + + +def test_case_10(): + """All arguments can be supplied through variable keyword arguments.""" + pytorch_code = textwrap.dedent( + """ + import torch + + events = [] + + class Scale(torch.nn.Module): + def forward(self, x, scale=1.0): + return x * scale + + def hook(module, args, kwargs): + events.append(len(kwargs)) + return (args[0] + 2,), kwargs + + net = Scale() + hook_kwargs = { + "hook": hook, + "prepend": False, + "with_kwargs": True, + } + net.register_forward_pre_hook(**hook_kwargs) + result = net(torch.tensor([-1.0, 2.0]), scale=3.0) + """ ) + obj.run(pytorch_code, ["events", "result"])