| @@ -0,0 +1,96 @@ | |||
| 1 | +# Copyright (c) 2026 Huawei Technologies Co., Ltd. All rights reserved. | ||
| 2 | +# | ||
| 3 | +# Licensed under the BSD 3-Clause License (the "License"); | ||
| 4 | +# you may not use this file except in compliance with the License. | ||
| 5 | +# You may obtain a copy of the License at | ||
| 6 | +# | ||
| 7 | +# https://opensource.org/licenses/BSD-3-Clause | ||
| 8 | +# | ||
| 9 | +# Unless required by applicable law or agreed to in writing, software | ||
| 10 | +# distributed under the License is distributed on an "AS IS" BASIS, | ||
| 11 | +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 12 | +# See the License for the specific language governing permissions and | ||
| 13 | +# limitations under the License. | ||
| 14 | + | ||
| 15 | +""" | ||
| 16 | +Minimal direct-API validation for the two AOT joint-with-descriptors entrypoints on NPU: | ||
| 17 | +1. torch._functorch.aot_autograd.aot_export_joint_with_descriptors | ||
| 18 | +2. torch._functorch.aot_autograd.aot_compile_joint_with_descriptors | ||
| 19 | + | ||
| 20 | +This file deliberately avoids reusing the upstream | ||
| 21 | +test/functorch/test_aot_joint_with_descriptors.py scaffolding (named module | ||
| 22 | +classes, assertExpectedInline FX graph text comparison, decomposition_table, | ||
| 23 | +full backward correctness). Instead it asserts only the observable contract | ||
| 24 | +of the API pair on NPU against an eager reference built from nn.Sequential: | ||
| 25 | +the export call returns a JointWithDescriptors exposing graph_module and | ||
| 26 | +_aot_state; the compile call returns a callable whose forward result matches | ||
| 27 | +the eager module. | ||
| 28 | + | ||
| 29 | +Invocation contract: the callable returned by aot_compile_joint_with_descriptors | ||
| 30 | +flattens (params, inputs) into positional args via fx_pytree, so it must be | ||
| 31 | +called as compiled(*params, *inputs), matching the upstream release/2.9+ | ||
| 32 | +test convention `parallel_model_fn(*dict(model.named_parameters()).values(), *inputs)`. | ||
| 33 | +""" | ||
| 34 | +from contextlib import ExitStack | ||
| 35 | + | ||
| 36 | +import torch | ||
| 37 | + | ||
| 38 | +from torch._functorch.aot_autograd import ( | ||
| 39 | + aot_compile_joint_with_descriptors, | ||
| 40 | + aot_export_joint_with_descriptors, | ||
| 41 | +) | ||
| 42 | +from torch.testing._internal.common_utils import run_tests, TestCase | ||
| 43 | + | ||
| 44 | +device_type = ( | ||
| 45 | + acc.type if (acc := torch.accelerator.current_accelerator()) else "cpu" | ||
| 46 | +) | ||
| 47 | + | ||
| 48 | + | ||
| 49 | +def _build_export_target(): | ||
| 50 | + """An eager nn.Sequential reference plus matching inputs on the active device.""" | ||
| 51 | + model = torch.nn.Sequential(torch.nn.Linear(2, 1)).to(device_type) | ||
| 52 | + inputs = (torch.randn(3, 2, device=device_type),) | ||
| 53 | + return model, inputs | ||
| 54 | + | ||
| 55 | + | ||
| 56 | +class TestAOTJointWithDescriptorsNPU(TestCase): | ||
| 57 | + def test_export_returns_joint_with_descriptors(self): | ||
| 58 | + """aot_export_joint_with_descriptors returns a JointWithDescriptors | ||
| 59 | + exposing graph_module and _aot_state on NPU. | ||
| 60 | + """ | ||
| 61 | + model, inputs = _build_export_target() | ||
| 62 | + with ExitStack() as stack: | ||
| 63 | + exported = aot_export_joint_with_descriptors(stack, model, inputs) | ||
| 64 | + self.assertIsNotNone(exported) | ||
| 65 | + self.assertIsNotNone(exported.graph_module) | ||
| 66 | + self.assertIsNotNone(exported._aot_state) | ||
| 67 | + | ||
| 68 | + def test_export_preserves_npu_device(self): | ||
| 69 | + """aot_export_joint_with_descriptors leaves input tensors and | ||
| 70 | + module parameters on the NPU device. | ||
| 71 | + """ | ||
| 72 | + model, inputs = _build_export_target() | ||
| 73 | + with ExitStack() as stack: | ||
| 74 | + aot_export_joint_with_descriptors(stack, model, inputs) | ||
| 75 | + self.assertEqual(inputs[0].device.type, device_type) | ||
| 76 | + for p in model.parameters(): | ||
| 77 | + self.assertEqual(p.device.type, device_type) | ||
| 78 | + | ||
| 79 | + def test_compile_runs_and_matches_eager(self): | ||
| 80 | + """aot_compile_joint_with_descriptors runs end-to-end on NPU and | ||
| 81 | + the compiled forward matches the eager module output. | ||
| 82 | + """ | ||
| 83 | + model, inputs = _build_export_target() | ||
| 84 | + with ExitStack() as stack: | ||
| 85 | + exported = aot_export_joint_with_descriptors(stack, model, inputs) | ||
| 86 | + compiled = aot_compile_joint_with_descriptors(exported) | ||
| 87 | + self.assertTrue(callable(compiled)) | ||
| 88 | + expected = model(*inputs) | ||
| 89 | + actual = compiled(*dict(model.named_parameters()).values(), *inputs) | ||
| 90 | + self.assertEqual(actual.shape, expected.shape) | ||
| 91 | + self.assertEqual(actual.device, expected.device) | ||
| 92 | + self.assertEqual(actual, expected, rtol=1e-3, atol=1e-3) | ||
| 93 | + | ||
| 94 | + | ||
| 95 | +if __name__ == "__main__": | ||
| 96 | + run_tests() | ||
| @@ -0,0 +1,102 @@ | |||
| 1 | +# Copyright (c) 2026 Huawei Technologies Co., Ltd. All rights reserved. | ||
| 2 | +# | ||
| 3 | +# Licensed under the BSD 3-Clause License (the "License"); | ||
| 4 | +# you may not use this file except in compliance with the License. | ||
| 5 | +# You may obtain a copy of the License at | ||
| 6 | +# | ||
| 7 | +# https://opensource.org/licenses/BSD-3-Clause | ||
| 8 | +# | ||
| 9 | +# Unless required by applicable law or agreed to in writing, software | ||
| 10 | +# distributed under the License is distributed on an "AS IS" BASIS, | ||
| 11 | +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 12 | +# See the License for the specific language governing permissions and | ||
| 13 | +# limitations under the License. | ||
| 14 | + | ||
| 15 | +""" | ||
| 16 | +Add validation cases for torch._functorch.config.patch API on NPU: | ||
| 17 | +1. PyTorch community lacks sufficient and direct API validations for this API, so this file is added. | ||
| 18 | +2. This file validates torch._functorch.config.patch (extendable). | ||
| 19 | +""" | ||
| 20 | +import torch | ||
| 21 | +import torch._functorch.config as config | ||
| 22 | +from torch.testing._internal.common_utils import run_tests, TestCase | ||
| 23 | + | ||
| 24 | +device_type = ( | ||
| 25 | + acc.type if (acc := torch.accelerator.current_accelerator()) else "cpu" | ||
| 26 | +) | ||
| 27 | + | ||
| 28 | + | ||
| 29 | +class TestFunctorchConfigPatch(TestCase): | ||
| 30 | + def test_basic_patch(self): | ||
| 31 | + """Test basic config.patch context manager""" | ||
| 32 | + original_value = config.debug_assert | ||
| 33 | + with config.patch("debug_assert", not original_value): | ||
| 34 | + self.assertEqual(config.debug_assert, not original_value) | ||
| 35 | + self.assertEqual(config.debug_assert, original_value) | ||
| 36 | + | ||
| 37 | + def test_patch_dict(self): | ||
| 38 | + """Test config.patch with dict argument""" | ||
| 39 | + original_debug = config.debug_assert | ||
| 40 | + original_cse = config.cse | ||
| 41 | + patches = { | ||
| 42 | + "debug_assert": not original_debug, | ||
| 43 | + "cse": not original_cse, | ||
| 44 | + } | ||
| 45 | + with config.patch(patches): | ||
| 46 | + self.assertEqual(config.debug_assert, not original_debug) | ||
| 47 | + self.assertEqual(config.cse, not original_cse) | ||
| 48 | + self.assertEqual(config.debug_assert, original_debug) | ||
| 49 | + self.assertEqual(config.cse, original_cse) | ||
| 50 | + | ||
| 51 | + def test_patch_restore_after_exception(self): | ||
| 52 | + """Test that config is restored even after an exception""" | ||
| 53 | + original_value = config.debug_assert | ||
| 54 | + with self.assertRaises(RuntimeError): | ||
| 55 | + with config.patch("debug_assert", not original_value): | ||
| 56 | + self.assertEqual(config.debug_assert, not original_value) | ||
| 57 | + raise RuntimeError("test exception") | ||
| 58 | + self.assertEqual(config.debug_assert, original_value) | ||
| 59 | + | ||
| 60 | + def test_patch_nested(self): | ||
| 61 | + """Test nested config.patch contexts""" | ||
| 62 | + original_value = config.debug_assert | ||
| 63 | + # First level | ||
| 64 | + with config.patch("debug_assert", True): | ||
| 65 | + self.assertTrue(config.debug_assert) | ||
| 66 | + # Second level (nested) | ||
| 67 | + with config.patch("debug_assert", False): | ||
| 68 | + self.assertFalse(config.debug_assert) | ||
| 69 | + # Back to first level | ||
| 70 | + self.assertTrue(config.debug_assert) | ||
| 71 | + # Back to original | ||
| 72 | + self.assertEqual(config.debug_assert, original_value) | ||
| 73 | + | ||
| 74 | + def test_patch_with_tensor_device(self): | ||
| 75 | + """Test that config.patch works correctly with NPU tensors in the context""" | ||
| 76 | + x = torch.randn(3, 4).to(device_type) | ||
| 77 | + original_value = config.debug_assert | ||
| 78 | + with config.patch("debug_assert", not original_value): | ||
| 79 | + # Verify NPU tensor operations still work | ||
| 80 | + y = x.mm(x.T) | ||
| 81 | + self.assertEqual(y.device.type, device_type) | ||
| 82 | + self.assertEqual(config.debug_assert, not original_value) | ||
| 83 | + self.assertEqual(config.debug_assert, original_value) | ||
| 84 | + | ||
| 85 | + def test_patch_invalid_key(self): | ||
| 86 | + """Test patch with invalid key raises AttributeError.""" | ||
| 87 | + with self.assertRaises(AttributeError): | ||
| 88 | + with config.patch("non_existent_key", 42): | ||
| 89 | + pass | ||
| 90 | + | ||
| 91 | + def test_patch_invalid_dict_key(self): | ||
| 92 | + """Test patch dict with invalid key among valid keys raises AttributeError and does not change valid keys.""" | ||
| 93 | + original_value = config.debug_assert | ||
| 94 | + with self.assertRaises(AttributeError): | ||
| 95 | + with config.patch({"debug_assert": True, "invalid_key": 42}): | ||
| 96 | + pass | ||
| 97 | + # Verify valid key is unchanged after the failed patch | ||
| 98 | + self.assertEqual(config.debug_assert, original_value) | ||
| 99 | + | ||
| 100 | + | ||
| 101 | +if __name__ == "__main__": | ||
| 102 | + run_tests() | ||
| @@ -0,0 +1,184 @@ | |||||||||||||
| 1 | +# Copyright (c) 2026 Huawei Technologies Co., Ltd. All rights reserved. | ||||||||||||
| 2 | +# | ||||||||||||
| 3 | +# Licensed under the BSD 3-Clause License (the "License"); | ||||||||||||
| 4 | +# you may not use this file except in compliance with the License. | ||||||||||||
| 5 | +# You may obtain a copy of the License at | ||||||||||||
| 6 | +# | ||||||||||||
| 7 | +# https://opensource.org/licenses/BSD-3-Clause | ||||||||||||
| 8 | +# | ||||||||||||
| 9 | +# Unless required by applicable law or agreed to in writing, software | ||||||||||||
| 10 | +# distributed under the License is distributed on an "AS IS" BASIS, | ||||||||||||
| 11 | +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||||||||||
| 12 | +# See the License for the specific language governing permissions and | ||||||||||||
| 13 | +# limitations under the License. | ||||||||||||
| 14 | + | ||||||||||||
| 15 | +""" | ||||||||||||
| 16 | +Add validation cases for torch._functorch.vmap._add_batch_dim API on NPU: | ||||||||||||
| 17 | +1. PyTorch community lacks sufficient and direct API validations for this API, so this file is added. | ||||||||||||
| 18 | +2. This file validates torch._functorch.vmap._add_batch_dim (extendable). | ||||||||||||
| 19 | +""" | ||||||||||||
| 20 | +import torch | ||||||||||||
| 21 | +from torch._functorch.vmap import _add_batch_dim | ||||||||||||
| 22 | +from torch.testing._internal.common_utils import run_tests, TestCase | ||||||||||||
🔴 Critical 文件 对比同目录下已有测试文件 触发条件:在 NPU 环境下运行此测试文件时,所有测试方法均会因 NPU 设备未注册而失败。 建议:在 import torch 之后添加 改动建议
![]() ![]() | |||||||||||||
| 23 | + | ||||||||||||
| 24 | +device_type = ( | ||||||||||||
| 25 | + acc.type if (acc := torch.accelerator.current_accelerator()) else "cpu" | ||||||||||||
| 26 | +) | ||||||||||||
| 27 | + | ||||||||||||
| 28 | + | ||||||||||||
| 29 | +class TestVmapAddBatchDim(TestCase): | ||||||||||||
| 30 | + def test_add_batch_dim_basic(self): | ||||||||||||
| 31 | + """Test basic _add_batch_dim functionality on NPU""" | ||||||||||||
| 32 | + x = torch.randn(3, 4).to(device_type) | ||||||||||||
| 33 | + vmap_level = 0 | ||||||||||||
| 34 | + batched = _add_batch_dim(x, 0, vmap_level) | ||||||||||||
| 35 | + self.assertIsNotNone(batched) | ||||||||||||
| 36 | + self.assertIsInstance(batched, torch.Tensor) | ||||||||||||
| 37 | + self.assertEqual(batched.device.type, device_type) | ||||||||||||
| 38 | + self.assertEqual(batched.shape, (4,)) | ||||||||||||
| 39 | + | ||||||||||||
| 40 | + def test_add_batch_dim_with_vmap(self): | ||||||||||||
| 41 | + """Test _add_batch_dim works correctly with vmap on NPU""" | ||||||||||||
| 42 | + x = torch.randn(2, 3).to(device_type) | ||||||||||||
| 43 | + y = torch.randn(2, 3).to(device_type) | ||||||||||||
| 44 | + | ||||||||||||
| 45 | + def dot_row(a, b): | ||||||||||||
| 46 | + return (a * b).sum(dim=-1) | ||||||||||||
| 47 | + | ||||||||||||
| 48 | + result = torch.vmap(dot_row)(x, y) | ||||||||||||
| 49 | + expected = dot_row(x, y) | ||||||||||||
| 50 | + self.assertEqual(result, expected) | ||||||||||||
| 51 | + self.assertEqual(result.device.type, device_type) | ||||||||||||
| 52 | + | ||||||||||||
| 53 | + def test_add_batch_dim_nested_vmap(self): | ||||||||||||
| 54 | + """Test nested vmap with _add_batch_dim on NPU""" | ||||||||||||
| 55 | + x = torch.randn(2, 3, 4).to(device_type) | ||||||||||||
| 56 | + y = torch.randn(2, 3, 4).to(device_type) | ||||||||||||
| 57 | + | ||||||||||||
| 58 | + def matmul_row(a, b): | ||||||||||||
| 59 | + return (a * b).sum(dim=-1) | ||||||||||||
| 60 | + | ||||||||||||
| 61 | + result = torch.vmap(torch.vmap(matmul_row))(x, y) | ||||||||||||
| 62 | + self.assertEqual(result.shape, (2, 3)) | ||||||||||||
| 63 | + self.assertEqual(result.device.type, device_type) | ||||||||||||
| 64 | + | ||||||||||||
| 65 | + def test_add_batch_dim_with_model(self): | ||||||||||||
| 66 | + """Test _add_batch_dim with a simple model on NPU""" | ||||||||||||
| 67 | + model = torch.nn.Linear(4, 2).to(device_type) | ||||||||||||
| 68 | + x = torch.randn(3, 4).to(device_type) | ||||||||||||
| 69 | + | ||||||||||||
| 70 | + result = torch.vmap(lambda x: model(x))(x) | ||||||||||||
| 71 | + expected = model(x) | ||||||||||||
| 72 | + self.assertEqual(result, expected) | ||||||||||||
| 73 | + self.assertEqual(result.device.type, device_type) | ||||||||||||
| 74 | + | ||||||||||||
| 75 | + def test_add_batch_dim_in_dims(self): | ||||||||||||
| 76 | + """Test _add_batch_dim with different in_dims on NPU""" | ||||||||||||
| 77 | + x = torch.randn(3, 4, 5).to(device_type) | ||||||||||||
| 78 | + | ||||||||||||
| 79 | + def identity(x): | ||||||||||||
| 80 | + return x | ||||||||||||
| 81 | + | ||||||||||||
| 82 | + # Test in_dims=0 (default) | ||||||||||||
| 83 | + result0 = torch.vmap(identity, in_dims=0)(x) | ||||||||||||
| 84 | + self.assertEqual(result0.shape, (3, 4, 5)) | ||||||||||||
| 85 | + | ||||||||||||
| 86 | + # Test in_dims=1 | ||||||||||||
| 87 | + result1 = torch.vmap(identity, in_dims=1)(x) | ||||||||||||
| 88 | + self.assertEqual(result1.shape, (4, 3, 5)) | ||||||||||||
| 89 | + | ||||||||||||
| 90 | + # Test in_dims=-1 | ||||||||||||
| 91 | + result_neg1 = torch.vmap(identity, in_dims=-1)(x) | ||||||||||||
| 92 | + self.assertEqual(result_neg1.shape, (5, 3, 4)) | ||||||||||||
| 93 | + | ||||||||||||
| 94 | + def test_add_batch_dim_out_dims(self): | ||||||||||||
| 95 | + """Test _add_batch_dim with different out_dims on NPU""" | ||||||||||||
| 96 | + x = torch.randn(3, 4).to(device_type) | ||||||||||||
| 97 | + | ||||||||||||
| 98 | + def identity(x): | ||||||||||||
| 99 | + return x | ||||||||||||
| 100 | + | ||||||||||||
| 101 | + # Test out_dims=0 (default) | ||||||||||||
| 102 | + result0 = torch.vmap(identity, out_dims=0)(x) | ||||||||||||
| 103 | + self.assertEqual(result0.shape, (3, 4)) | ||||||||||||
| 104 | + | ||||||||||||
| 105 | + # Test out_dims=1 | ||||||||||||
| 106 | + result1 = torch.vmap(identity, out_dims=1)(x) | ||||||||||||
| 107 | + self.assertEqual(result1.shape, (4, 3)) | ||||||||||||
| 108 | + | ||||||||||||
| 109 | + def test_add_batch_dim_with_grad(self): | ||||||||||||
| 110 | + """Test _add_batch_dim works with gradient computation on NPU""" | ||||||||||||
| 111 | + x = torch.randn(3, 3, device=device_type, requires_grad=True) | ||||||||||||
| 112 | + w = torch.randn(3, 3, device=device_type, requires_grad=True) | ||||||||||||
| 113 | + | ||||||||||||
| 114 | + def fn(x, w): | ||||||||||||
| 115 | + return (x * w).sum(dim=-1) | ||||||||||||
| 116 | + | ||||||||||||
| 117 | + result = torch.vmap(fn)(x, w) | ||||||||||||
| 118 | + loss = result.sum() | ||||||||||||
| 119 | + loss.backward() | ||||||||||||
| 120 | + self.assertIsNotNone(x.grad) | ||||||||||||
| 121 | + self.assertIsNotNone(w.grad) | ||||||||||||
| 122 | + self.assertEqual(x.grad.device.type, device_type) | ||||||||||||
| 123 | + self.assertEqual(w.grad.device.type, device_type) | ||||||||||||
| 124 | + | ||||||||||||
| 125 | + def test_add_batch_dim_direct_3d_batch_dim_0(self): | ||||||||||||
| 126 | + """Direct API call: 3D tensor with batch_dim=0 returns shape=(4,5) (inner value sliced at dim 0).""" | ||||||||||||
| 127 | + x = torch.randn(3, 4, 5, dtype=torch.float32).to(device_type) | ||||||||||||
| 128 | + vmap_level = 0 | ||||||||||||
| 129 | + batched = _add_batch_dim(x, 0, vmap_level) | ||||||||||||
| 130 | + self.assertIsNotNone(batched) | ||||||||||||
| 131 | + self.assertEqual(batched.shape, (4, 5)) | ||||||||||||
| 132 | + self.assertEqual(batched.device.type, device_type) | ||||||||||||
| 133 | + self.assertEqual(batched.dtype, torch.float32) | ||||||||||||
| 134 | + | ||||||||||||
| 135 | + def test_add_batch_dim_direct_3d_batch_dim_1(self): | ||||||||||||
| 136 | + """Direct API call: 3D tensor with batch_dim=1 returns shape=(3,5).""" | ||||||||||||
| 137 | + x = torch.randn(3, 4, 5, dtype=torch.float32).to(device_type) | ||||||||||||
| 138 | + vmap_level = 0 | ||||||||||||
| 139 | + batched = _add_batch_dim(x, 1, vmap_level) | ||||||||||||
| 140 | + self.assertIsNotNone(batched) | ||||||||||||
| 141 | + self.assertEqual(batched.shape, (3, 5)) | ||||||||||||
| 142 | + self.assertEqual(batched.device.type, device_type) | ||||||||||||
| 143 | + | ||||||||||||
| 144 | + def test_add_batch_dim_direct_3d_batch_dim_2(self): | ||||||||||||
| 145 | + """Direct API call: 3D tensor with batch_dim=2 returns shape=(3,4).""" | ||||||||||||
| 146 | + x = torch.randn(3, 4, 5, dtype=torch.float32).to(device_type) | ||||||||||||
| 147 | + vmap_level = 0 | ||||||||||||
| 148 | + batched = _add_batch_dim(x, 2, vmap_level) | ||||||||||||
| 149 | + self.assertIsNotNone(batched) | ||||||||||||
| 150 | + self.assertEqual(batched.shape, (3, 4)) | ||||||||||||
| 151 | + self.assertEqual(batched.device.type, device_type) | ||||||||||||
| 152 | + | ||||||||||||
| 153 | + def test_add_batch_dim_direct_preserves_dtype_and_device(self): | ||||||||||||
| 154 | + """Direct API call preserves dtype and device.""" | ||||||||||||
| 155 | + x = torch.tensor([[1.0, 2.0], [3.0, 4.0]], dtype=torch.float32).to(device_type) | ||||||||||||
| 156 | + vmap_level = 0 | ||||||||||||
| 157 | + batched = _add_batch_dim(x, 0, vmap_level) | ||||||||||||
| 158 | + self.assertEqual(batched.dtype, torch.float32) | ||||||||||||
| 159 | + self.assertEqual(batched.device.type, device_type) | ||||||||||||
| 160 | + self.assertEqual(batched.shape, (2,)) | ||||||||||||
| 161 | + | ||||||||||||
| 162 | + def test_add_batch_dim_invalid_batch_dim(self): | ||||||||||||
| 163 | + """Direct API call with out-of-range batch_dim wraps (x.ndim % batch_dim).""" | ||||||||||||
| 164 | + x = torch.randn(3, 4).to(device_type) | ||||||||||||
| 165 | + # batch_dim=5 on 2D tensor acts as batch_dim=1 (5 % 2 ≡ 1) | ||||||||||||
| 166 | + batched = _add_batch_dim(x, 5, 0) | ||||||||||||
| 167 | + self.assertEqual(batched.shape, (3,)) | ||||||||||||
| 168 | + self.assertEqual(batched.device.type, device_type) | ||||||||||||
| 169 | + | ||||||||||||
| 170 | + def test_add_batch_dim_multiple_levels(self): | ||||||||||||
| 171 | + """Test different vmap_levels produce independent batch dimensions.""" | ||||||||||||
| 172 | + from torch._functorch.vmap import _remove_batch_dim | ||||||||||||
| 173 | + x = torch.randn(3, 4).to(device_type) | ||||||||||||
| 174 | + # Level 0 | ||||||||||||
| 175 | + b_l0 = _add_batch_dim(x, 0, 0) | ||||||||||||
| 176 | + # Level 1 - independent from level 0 | ||||||||||||
| 177 | + b_l1 = _add_batch_dim(x, 0, 1) | ||||||||||||
| 178 | + # Both levels show the same shape on the surface | ||||||||||||
| 179 | + self.assertEqual(b_l0.shape, (4,)) | ||||||||||||
| 180 | + self.assertEqual(b_l1.device.type, device_type) | ||||||||||||
| 181 | + | ||||||||||||
| 182 | + | ||||||||||||
| 183 | +if __name__ == "__main__": | ||||||||||||
| 184 | + run_tests() | ||||||||||||
| @@ -0,0 +1,236 @@ | |||||||||||
| 1 | +diff --git a/test/functorch/test_aot_joint_with_descriptors.py b/test/functorch/test_aot_joint_with_descriptors.py | ||||||||||
| 2 | +index 13f2318..0dfba56 100644 | ||||||||||
| 3 | +--- a/test/functorch/test_aot_joint_with_descriptors.py | ||||||||||
| 4 | ++++ b/test/functorch/test_aot_joint_with_descriptors.py | ||||||||||
| 5 | + from torch._functorch.aot_autograd import ( | ||||||||||
| 6 | + from torch._guards import tracing, TracingContext | ||||||||||
| 7 | + from torch.nn.attention.flex_attention import create_block_mask, flex_attention | ||||||||||
| 8 | + from torch.testing._internal.common_utils import ( | ||||||||||
| 9 | +- requires_cuda, | ||||||||||
| 10 | + run_tests, | ||||||||||
| 11 | + skipIfCrossRef, | ||||||||||
| 12 | + TestCase, | ||||||||||
| 13 | + ) | ||||||||||
| 14 | + | ||||||||||
| 15 | + | ||||||||||
| 16 | ++device_type = ( | ||||||||||
| 17 | ++ acc.type if (acc := torch.accelerator.current_accelerator()) else "cpu" | ||||||||||
| 18 | ++) | ||||||||||
| 19 | ++ | ||||||||||
| 20 | ++ | ||||||||||
| 21 | + def graph_capture(model, inputs, with_export): | ||||||||||
| 22 | + gm = model | ||||||||||
| 23 | + tracing_context = None | ||||||||||
| 24 | + class TestAOTJointWithDescriptors(TestCase): | ||||||||||
| 25 | + def forward(self, x): | ||||||||||
| 26 | + return self.linear(x) | ||||||||||
| 27 | + | ||||||||||
| 28 | +- model = SimpleLinear() | ||||||||||
| 29 | +- inputs = (torch.randn(4, 3),) | ||||||||||
| 30 | ++ model = SimpleLinear().to(device_type) | ||||||||||
| 31 | ++ inputs = (torch.randn(4, 3, device=device_type),) | ||||||||||
| 32 | + | ||||||||||
| 33 | + with ExitStack() as stack: | ||||||||||
| 34 | + # Export joint with descriptors | ||||||||||
| 35 | + class inner_f(torch.nn.Module): | ||||||||||
| 36 | + x = self.bn(x) | ||||||||||
| 37 | + return torch.relu(x) | ||||||||||
| 38 | + | ||||||||||
| 39 | +- model = ConvBN() | ||||||||||
| 40 | ++ model = ConvBN().to(device_type) | ||||||||||
| 41 | + model.train() # Important for batch norm | ||||||||||
| 42 | +- inputs = (torch.randn(2, 1, 4, 4),) | ||||||||||
| 43 | ++ inputs = (torch.randn(2, 1, 4, 4, device=device_type),) | ||||||||||
| 44 | + | ||||||||||
| 45 | + with ExitStack() as stack: | ||||||||||
| 46 | + # Export joint with descriptors | ||||||||||
| 47 | + class inner_f(torch.nn.Module): | ||||||||||
| 48 | + def forward(self, x, *, scale): | ||||||||||
| 49 | + return self.linear(x) * scale | ||||||||||
| 50 | + | ||||||||||
| 51 | +- model = ModuleWithKwargs() | ||||||||||
| 52 | +- inputs = (torch.randn(4, 3),) | ||||||||||
| 53 | +- kwargs = {"scale": torch.tensor(2.0)} | ||||||||||
| 54 | ++ model = ModuleWithKwargs().to(device_type) | ||||||||||
| 55 | ++ inputs = (torch.randn(4, 3, device=device_type),) | ||||||||||
| 56 | ++ kwargs = {"scale": torch.tensor(2.0, device=device_type)} | ||||||||||
| 57 | + | ||||||||||
| 58 | + gm = dynamo_graph_capture_for_export(model)(*inputs, **kwargs) | ||||||||||
| 59 | + | ||||||||||
| 60 | + class inner_f(torch.nn.Module): | ||||||||||
| 61 | + out2 = self.linear2(x) | ||||||||||
| 62 | + return out1, out2 | ||||||||||
| 63 | + | ||||||||||
| 64 | +- model = MultiOutputModule() | ||||||||||
| 65 | +- inputs = (torch.randn(4, 3),) | ||||||||||
| 66 | ++ model = MultiOutputModule().to(device_type) | ||||||||||
| 67 | ++ inputs = (torch.randn(4, 3, device=device_type),) | ||||||||||
| 68 | + | ||||||||||
| 69 | + with ExitStack() as stack: | ||||||||||
| 70 | + # Export joint with descriptors | ||||||||||
| 71 | + class inner_f(torch.nn.Module): | ||||||||||
| 72 | + def forward(self, x): | ||||||||||
| 73 | + return self.linear(x) | ||||||||||
| 74 | + | ||||||||||
| 75 | +- model = SimpleModule() | ||||||||||
| 76 | +- inputs = (torch.randn(4, 3),) | ||||||||||
| 77 | ++ model = SimpleModule().to(device_type) | ||||||||||
| 78 | ++ inputs = (torch.randn(4, 3, device=device_type),) | ||||||||||
| 79 | + | ||||||||||
| 80 | + with ExitStack() as stack: | ||||||||||
| 81 | + # Export joint with descriptors | ||||||||||
| 82 | + class inner_f(torch.nn.Module): | ||||||||||
| 83 | + def forward(self, x): | ||||||||||
| 84 | + return self.linear(x) | ||||||||||
| 85 | + | ||||||||||
| 86 | +- model = SimpleLinear() | ||||||||||
| 87 | +- inputs = (torch.randn(4, 3),) | ||||||||||
| 88 | ++ model = SimpleLinear().to(device_type) | ||||||||||
| 89 | ++ inputs = (torch.randn(4, 3, device=device_type),) | ||||||||||
| 90 | + | ||||||||||
| 91 | + with ExitStack() as stack: | ||||||||||
| 92 | + joint_with_descriptors = aot_export_joint_with_descriptors( | ||||||||||
| 93 | + class inner_f(torch.nn.Module): | ||||||||||
| 94 | + x = self.bn(x) | ||||||||||
| 95 | + return torch.relu(x) | ||||||||||
| 96 | + | ||||||||||
| 97 | +- model = ConvBN() | ||||||||||
| 98 | ++ model = ConvBN().to(device_type) | ||||||||||
| 99 | + model.train() # Important for batch norm | ||||||||||
| 100 | +- inputs = (torch.randn(2, 1, 4, 4),) | ||||||||||
| 101 | ++ inputs = (torch.randn(2, 1, 4, 4, device=device_type),) | ||||||||||
| 102 | + | ||||||||||
| 103 | + with ExitStack() as stack: | ||||||||||
| 104 | + joint_with_descriptors = aot_export_joint_with_descriptors( | ||||||||||
| 105 | + class inner_f(torch.nn.Module): | ||||||||||
| 106 | + out2 = self.linear2(x) | ||||||||||
| 107 | + return out1, out2 | ||||||||||
| 108 | + | ||||||||||
| 109 | +- model = MultiOutputModule() | ||||||||||
| 110 | +- inputs = (torch.randn(4, 3),) | ||||||||||
| 111 | ++ model = MultiOutputModule().to(device_type) | ||||||||||
| 112 | ++ inputs = (torch.randn(4, 3, device=device_type),) | ||||||||||
| 113 | + | ||||||||||
| 114 | + with ExitStack() as stack: | ||||||||||
| 115 | + joint_with_descriptors = aot_export_joint_with_descriptors( | ||||||||||
| 116 | + class inner_f(torch.nn.Module): | ||||||||||
| 117 | + def forward(self, x): | ||||||||||
| 118 | + return self.linear(x) | ||||||||||
| 119 | + | ||||||||||
| 120 | +- model = SimpleModule() | ||||||||||
| 121 | +- inputs = (torch.randn(4, 3),) | ||||||||||
| 122 | ++ model = SimpleModule().to(device_type) | ||||||||||
| 123 | ++ inputs = (torch.randn(4, 3, device=device_type),) | ||||||||||
| 124 | + | ||||||||||
| 125 | + with ExitStack() as stack: | ||||||||||
| 126 | + joint_with_descriptors = aot_export_joint_with_descriptors( | ||||||||||
| 127 | + class inner_f(torch.nn.Module): | ||||||||||
| 128 | + def forward(self, x): | ||||||||||
| 129 | + return self.linear(x) | ||||||||||
| 130 | + | ||||||||||
| 131 | +- model = SimpleModule() | ||||||||||
| 132 | +- inputs = (torch.randn(4, 3),) | ||||||||||
| 133 | ++ model = SimpleModule().to(device_type) | ||||||||||
| 134 | ++ inputs = (torch.randn(4, 3, device=device_type),) | ||||||||||
| 135 | + | ||||||||||
| 136 | + with ExitStack() as stack: | ||||||||||
| 137 | + joint_with_descriptors = aot_export_joint_with_descriptors( | ||||||||||
| 138 | + class inner_f(torch.nn.Module): | ||||||||||
| 139 | + y = self.linear(x) | ||||||||||
| 140 | + return y - 1 | ||||||||||
| 141 | + | ||||||||||
| 142 | +- inputs = (torch.randn(4, 3),) | ||||||||||
| 143 | +- model = SimpleLinear() | ||||||||||
| 144 | ++ inputs = (torch.randn(4, 3, device=device_type),) | ||||||||||
| 145 | ++ model = SimpleLinear().to(device_type) | ||||||||||
| 146 | + | ||||||||||
| 147 | + for with_export in [True, False]: | ||||||||||
| 148 | + graph_module = graph_capture(model, inputs, with_export) | ||||||||||
| 149 | + class inner_f(torch.nn.Module): | ||||||||||
| 150 | + ('call_function', 't_3', {'pp_stage': 0})""", | ||||||||||
| 151 | + ) | ||||||||||
| 152 | + | ||||||||||
已过期 🟠 High Priority 在 patch 文件第 153 行,对 此处先 触发条件:当 建议:将 改动建议
![]() ![]() 已过期 🟡 Medium Priority patch 第 153 行将 变更行:patch 第 153 行 建议:将 改动建议
![]() ![]() | |||||||||||
| 153 | +- @requires_cuda | ||||||||||
| 154 | + def test_preserve_annotate_flex_attention(self): | ||||||||||
| 155 | + def score_mod(score, b, h, m, n): | ||||||||||
| 156 | + return score | ||||||||||
| 157 | + class inner_f(torch.nn.Module): | ||||||||||
| 158 | + b = 24 | ||||||||||
| 159 | + batch_size = 2 | ||||||||||
| 160 | + seqlen = a * b | ||||||||||
| 161 | +- device = "cuda" | ||||||||||
| 162 | ++ device = device_type | ||||||||||
| 163 | + | ||||||||||
| 164 | + # Create seq_idx tensor - maps each position to a document/sequence ID | ||||||||||
| 165 | + # Example: Split sequence into 2 documents for each batch | ||||||||||
| 166 | + class inner_f(torch.nn.Module): | ||||||||||
| 167 | + y = example_function(y) | ||||||||||
| 168 | + return y - 1 | ||||||||||
| 169 | + | ||||||||||
| 170 | +- inputs = (torch.randn(4, 3),) | ||||||||||
| 171 | +- model = SimpleLinear() | ||||||||||
| 172 | ++ inputs = (torch.randn(4, 3, device=device_type),) | ||||||||||
| 173 | ++ model = SimpleLinear().to(device_type) | ||||||||||
| 174 | + | ||||||||||
| 175 | + for with_export in [True, False]: | ||||||||||
| 176 | + graph_module = graph_capture(model, inputs, with_export) | ||||||||||
| 177 | + class inner_f(torch.nn.Module): | ||||||||||
| 178 | + def forward(self, x, y): | ||||||||||
| 179 | + return foo(x, y) | ||||||||||
| 180 | + | ||||||||||
| 181 | +- model = CustomOpModule() | ||||||||||
| 182 | +- inputs = (torch.randn(4, 3), torch.randn(4, 3)) | ||||||||||
| 183 | ++ model = CustomOpModule().to(device_type) | ||||||||||
| 184 | ++ inputs = (torch.randn(4, 3, device=device_type), torch.randn(4, 3, device=device_type)) | ||||||||||
| 185 | + | ||||||||||
| 186 | + gm = graph_capture(model, inputs, with_export=True) | ||||||||||
| 187 | + | ||||||||||
| 188 | + class inner_f(torch.nn.Module): | ||||||||||
| 189 | + def __init__(self): | ||||||||||
| 190 | + super().__init__() | ||||||||||
| 191 | + self.input_shape = (5, 3) | ||||||||||
| 192 | +- self.permuted_indices = torch.tensor([2, 0, 3, 1]) | ||||||||||
| 193 | ++ self.permuted_indices = torch.tensor([2, 0, 3, 1], device=device_type) | ||||||||||
| 194 | + | ||||||||||
| 195 | + def forward(self, x): | ||||||||||
| 196 | + with fx_traceback.annotate({"pp_stage": 0}): | ||||||||||
| 197 | + class inner_f(torch.nn.Module): | ||||||||||
| 198 | + ) | ||||||||||
| 199 | + return routed_output.cos() | ||||||||||
| 200 | + | ||||||||||
| 201 | +- inputs = (torch.randn(4, 3, requires_grad=True),) | ||||||||||
| 202 | ++ inputs = (torch.randn(4, 3, device=device_type, requires_grad=True),) | ||||||||||
| 203 | + model = Module() | ||||||||||
| 204 | + | ||||||||||
| 205 | + graph_module = graph_capture(model, inputs, True) | ||||||||||
| 206 | + class inner_f(torch.nn.Module): | ||||||||||
| 207 | + def forward(self, x): | ||||||||||
| 208 | + return self.linear(x) | ||||||||||
| 209 | + | ||||||||||
| 210 | +- model = SimpleLinear() | ||||||||||
| 211 | +- inputs = (torch.randn(4, 3),) | ||||||||||
| 212 | ++ model = SimpleLinear().to(device_type) | ||||||||||
| 213 | ++ inputs = (torch.randn(4, 3, device=device_type),) | ||||||||||
| 214 | + gm = dynamo_graph_capture_for_export(model)(*inputs) | ||||||||||
| 215 | + fake_mode = gm.meta.get("fake_mode", None) | ||||||||||
| 216 | + | ||||||||||
| 217 | + class inner_f(torch.nn.Module): | ||||||||||
| 218 | + with fx_traceback.annotate({"test": 1}): | ||||||||||
| 219 | + return self.linear(x) - self.linear2(x) | ||||||||||
| 220 | + | ||||||||||
| 221 | +- model = SimpleLinear() | ||||||||||
| 222 | +- inputs = (torch.randn(4, 3, requires_grad=True),) | ||||||||||
| 223 | ++ model = SimpleLinear().to(device_type) | ||||||||||
| 224 | ++ inputs = (torch.randn(4, 3, device=device_type, requires_grad=True),) | ||||||||||
| 225 | + graph_module = graph_capture(model, inputs, True) | ||||||||||
| 226 | + add_nodes = graph_module.graph.find_nodes( | ||||||||||
| 227 | + op="call_function", target=torch.ops.aten.add.Tensor | ||||||||||
| 228 | + class inner_f(torch.nn.Module): | ||||||||||
| 229 | + z = self.bar(x) | ||||||||||
| 230 | + return z - 1 | ||||||||||
| 231 | + | ||||||||||
| 232 | +- inputs = (torch.randn(4, 3, requires_grad=True),) | ||||||||||
| 233 | ++ inputs = (torch.randn(4, 3, device=device_type, requires_grad=True),) | ||||||||||
| 234 | + model = MyMod() | ||||||||||
| 235 | + | ||||||||||
| 236 | + # invoke_subgraph doesn't seem to work with with_export=False, no subgraph created | ||||||||||


🔴 Critical
文件
test/functorch/test_functorch_config_api.py使用device_type = "npu"并通过.to(device_type)将张量移至 NPU 设备,但未导入torch_npu。在本仓库中,torch_npu负责注册 NPU 设备;若不导入,torch.randn(...).to("npu")将抛出RuntimeError: Device type 'npu' is not supported。对比同目录下已有测试文件
test/functorch/test_eager_transforms.py(第 25-26 行)明确导入了import torch_npu和import torch_npu.testing。触发条件:在 NPU 环境下运行此测试文件时,所有测试方法均会因 NPU 设备未注册而失败。
建议:在 import torch 之后添加
import torch_npu和import torch_npu.testing,与同目录下test_eager_transforms.py保持一致。from torch.testing._internal.common_utilsimport run_tests, TestCase