已合并
fix: npugraph_ex补充用户自定义backend和自定义pass缺失的接口 #30367
Xu Zhenhua创建于 2月2日
fix: npugraph_ex补充用户自定义backend和自定义pass缺失的接口 #30367
已合并
共 5 个文件变更+256-73
| @@ -1,18 +1,10 @@ | |||
| 1 | # Owner(s): ["module: dynamo"] | 1 | # Owner(s): ["module: dynamo"] |
| 2 | -import unittest | ||
| 3 | -import dataclasses | ||
| 4 | -from typing import List | ||
| 5 | - | ||
| 6 | import torch | 2 | import torch |
| 7 | from torch._dynamo.test_case import TestCase | 3 | from torch._dynamo.test_case import TestCase |
| 8 | -from torch._dynamo.testing import same | ||
| 9 | 4 | ||
| 10 | import torch_npu | 5 | import torch_npu |
| 11 | 6 | ||
| 12 | 7 | ||
| 13 | -DEVICE_NAME = torch_npu.npu.get_device_name(0)[:10] | ||
| 14 | - | ||
| 15 | - | ||
| 16 | class TestNpuBackend(TestCase): | 8 | class TestNpuBackend(TestCase): |
| 17 | def test_optimize_npu(self): | 9 | def test_optimize_npu(self): |
| 18 | func = torch.ops.aten.relu.default | 10 | func = torch.ops.aten.relu.default |
| @@ -23,62 +15,6 @@ class TestNpuBackend(TestCase): | |||
| 23 | dynamo_result = dynamo_func(x) | 15 | dynamo_result = dynamo_func(x) |
| 24 | self.assertEqual(eager_result, dynamo_result) | 16 | self.assertEqual(eager_result, dynamo_result) |
| 25 | 17 | ||
| 26 | - | ||
| 27 | - def test_npugraph_ex_backend(self): | ||
| 28 | - class Model(torch.nn.Module): | ||
| 29 | - def __init__(self): | ||
| 30 | - super().__init__() | ||
| 31 | - | ||
| 32 | - def forward(self, x, y): | ||
| 33 | - return x + y | ||
| 34 | - | ||
| 35 | - compiled_model = torch.compile(Model().npu(), backend="npugraph_ex", fullgraph=True, dynamic=False) | ||
| 36 | - x = torch.ones(1, dtype=torch.int32).npu() | ||
| 37 | - y = torch.ones(1, dtype=torch.int32).npu() | ||
| 38 | - z = compiled_model(x, y) | ||
| 39 | - self.assertEqual(z.item(), 2) | ||
| 40 | - | ||
| 41 | - | ||
| 42 | - def test_npugraph_ex_cache_compile(self): | ||
| 43 | - | ||
| 44 | - class InputMeta: | ||
| 45 | - data: torch.Tensor | ||
| 46 | - is_prompt: bool | ||
| 47 | - | ||
| 48 | - class Model(torch.nn.Module): | ||
| 49 | - def __init__(self): | ||
| 50 | - super().__init__() | ||
| 51 | - self.linear1 = torch.nn.Linear(2, 1) | ||
| 52 | - self.linear2 = torch.nn.Linear(2, 1) | ||
| 53 | - for param in self.parameters(): | ||
| 54 | - torch.nn.init.ones_(param) | ||
| 55 | - self.cached_prompt = torch.npu.npugraph_ex.inference.cache_compile(self.prompt) | ||
| 56 | - self.cached_decode = torch.npu.npugraph_ex.inference.cache_compile(self.decode) | ||
| 57 | - | ||
| 58 | - def forward(self, x: InputMeta, kv: List[torch.Tensor]): | ||
| 59 | - if x.is_prompt: | ||
| 60 | - return self.cached_prompt(x, kv) | ||
| 61 | - return self.cached_decode(x, kv) | ||
| 62 | - | ||
| 63 | - def _forward(self, x, kv): | ||
| 64 | - return self.linear2(x.data) + self.linear2(kv[0]) | ||
| 65 | - | ||
| 66 | - def prompt(self, x, y): | ||
| 67 | - return self._forward(x, y) | ||
| 68 | - | ||
| 69 | - def decode(self, x, y): | ||
| 70 | - return self._forward(x, y) | ||
| 71 | - | ||
| 72 | - x = InputMeta(data=torch.ones(2, 2).npu(), is_prompt=True) | ||
| 73 | - kv = [torch.ones(2, 2).npu()] | ||
| 74 | - model = Model().npu() | ||
| 75 | - res_prompt = model(x, kv) | ||
| 76 | - x.is_prompt = False | ||
| 77 | - res_decode = model(x, kv) | ||
| 78 | - res = torch.empty(2, 1).npu().fill_(6.0) | ||
| 79 | - self.assertTrue(same(res, res_prompt)) | ||
| 80 | - self.assertTrue(same(res, res_decode)) | ||
| 81 | - | ||
| 82 | 18 | ||
| 83 | if __name__ == "__main__": | 19 | if __name__ == "__main__": |
| 84 | from torch._dynamo.test_case import run_tests | 20 | from torch._dynamo.test_case import run_tests |
| @@ -0,0 +1,210 @@ | |||
| 1 | +# Owner(s): ["module: dynamo"] | ||
| 2 | +import dataclasses | ||
| 3 | +import functools | ||
| 4 | +import unittest | ||
| 5 | +from typing import List | ||
| 6 | + | ||
| 7 | +import torch | ||
| 8 | +from torch._dynamo.test_case import TestCase | ||
| 9 | +from torch._dynamo.testing import same | ||
| 10 | +from torch._functorch.aot_autograd import aot_module_simplified | ||
| 11 | +from torch._inductor.pattern_matcher import Match | ||
| 12 | +from torch._subclasses.fake_tensor import FakeTensorMode | ||
| 13 | + | ||
| 14 | +import torch_npu | ||
| 15 | + | ||
| 16 | +DEVICE_NAME = torch_npu.npu.get_device_name(0)[:10] | ||
| 17 | + | ||
| 18 | + | ||
| 19 | + | ||
| 20 | +class TestNpuGraphEx(TestCase): | ||
| 21 | + def test_backend(self): | ||
| 22 | + class Model(torch.nn.Module): | ||
| 23 | + def __init__(self): | ||
| 24 | + super().__init__() | ||
| 25 | + | ||
| 26 | + def forward(self, x, y): | ||
| 27 | + return x + y | ||
| 28 | + | ||
| 29 | + compiled_model = torch.compile(Model().npu(), backend="npugraph_ex", fullgraph=True, dynamic=False) | ||
| 30 | + x = torch.ones(1, dtype=torch.int32, device="npu") | ||
| 31 | + y = torch.ones(1, dtype=torch.int32, device="npu") | ||
| 32 | + z = compiled_model(x, y) | ||
| 33 | + self.assertEqual(z.item(), 2) | ||
| 34 | + | ||
| 35 | + def test_compile_fx(self): | ||
| 36 | + class Model(torch.nn.Module): | ||
| 37 | + def __init__(self): | ||
| 38 | + super().__init__() | ||
| 39 | + | ||
| 40 | + def forward(self, x, y): | ||
| 41 | + return x + y | ||
| 42 | + | ||
| 43 | + def my_backend(gm: torch.fx.GraphModule, example_inputs): | ||
| 44 | + compiler = torch.npu.npugraph_ex.compile_fx() | ||
| 45 | + return aot_module_simplified(gm, example_inputs, fw_compiler=compiler) | ||
| 46 | + | ||
| 47 | + compiled_model = torch.compile(Model().npu(), backend=my_backend, fullgraph=True, dynamic=False) | ||
| 48 | + x = torch.ones(1, dtype=torch.int32, device="npu") | ||
| 49 | + y = torch.ones(1, dtype=torch.int32, device="npu") | ||
| 50 | + z = compiled_model(x, y) | ||
| 51 | + self.assertEqual(z.item(), 2) | ||
| 52 | + | ||
| 53 | + def test_cache_compile(self): | ||
| 54 | + | ||
| 55 | + class InputMeta: | ||
| 56 | + data: torch.Tensor | ||
| 57 | + is_prompt: bool | ||
| 58 | + | ||
| 59 | + class Model(torch.nn.Module): | ||
| 60 | + def __init__(self): | ||
| 61 | + super().__init__() | ||
| 62 | + self.linear1 = torch.nn.Linear(2, 1) | ||
| 63 | + self.linear2 = torch.nn.Linear(2, 1) | ||
| 64 | + for param in self.parameters(): | ||
| 65 | + torch.nn.init.ones_(param) | ||
| 66 | + self.cached_prompt = torch.npu.npugraph_ex.inference.cache_compile(self.prompt) | ||
| 67 | + self.cached_decode = torch.npu.npugraph_ex.inference.cache_compile(self.decode) | ||
| 68 | + | ||
| 69 | + def forward(self, x: InputMeta, kv: List[torch.Tensor]): | ||
| 70 | + if x.is_prompt: | ||
| 71 | + return self.cached_prompt(x, kv) | ||
| 72 | + return self.cached_decode(x, kv) | ||
| 73 | + | ||
| 74 | + def _forward(self, x, kv): | ||
| 75 | + return self.linear2(x.data) + self.linear2(kv[0]) | ||
| 76 | + | ||
| 77 | + def prompt(self, x, y): | ||
| 78 | + return self._forward(x, y) | ||
| 79 | + | ||
| 80 | + def decode(self, x, y): | ||
| 81 | + return self._forward(x, y) | ||
| 82 | + | ||
| 83 | + x = InputMeta(data=torch.ones(2, 2).npu(), is_prompt=True) | ||
| 84 | + kv = [torch.ones(2, 2).npu()] | ||
| 85 | + model = Model().npu() | ||
| 86 | + res_prompt = model(x, kv) | ||
| 87 | + x.is_prompt = False | ||
| 88 | + res_decode = model(x, kv) | ||
| 89 | + res = torch.empty(2, 1).npu().fill_(6.0) | ||
| 90 | + self.assertTrue(same(res, res_prompt)) | ||
| 91 | + self.assertTrue(same(res, res_decode)) | ||
| 92 | + | ||
| 93 | + def test_register_replacement(self): | ||
| 94 | + def search_fn(x1, x2, gamma): | ||
| 95 | + x_out = torch.add(x1, x2) | ||
| 96 | + y, _ = torch_npu.npu_rms_norm(x_out, gamma) | ||
| 97 | + return y, x_out | ||
| 98 | + | ||
| 99 | + def replace_fn(x1, x2, gamma): | ||
| 100 | + y, _, x_out = torch_npu.npu_add_rms_norm( | ||
| 101 | + x1, x2, gamma | ||
| 102 | + ) | ||
| 103 | + return y, x_out | ||
| 104 | + | ||
| 105 | + def extra_check(match: Match): | ||
| 106 | + x1 = match.kwargs.get("x1") | ||
| 107 | + | ||
| 108 | + if x1 is None: | ||
| 109 | + return False | ||
| 110 | + if not hasattr(x1, "meta") or "val" not in x1.meta: | ||
| 111 | + return False | ||
| 112 | + | ||
| 113 | + a_shape = x1.meta["val"].shape | ||
| 114 | + return a_shape[-1] == 7168 | ||
| 115 | + | ||
| 116 | + fake_mode = FakeTensorMode() | ||
| 117 | + with fake_mode: | ||
| 118 | + input_tensor = functools.partial(torch.empty, (1, 1, 2), dtype=torch.float16, device="npu") | ||
| 119 | + kwargs_tensor = functools.partial(torch.empty, 2, dtype=torch.float16, device="npu") | ||
| 120 | + | ||
| 121 | + torch.npu.npugraph_ex.register_replacement( | ||
| 122 | + search_fn=search_fn, | ||
| 123 | + replace_fn=replace_fn, | ||
| 124 | + example_inputs=(input_tensor(), input_tensor(), kwargs_tensor()), | ||
| 125 | + extra_check=extra_check | ||
| 126 | + ) | ||
| 127 | + | ||
| 128 | + class Model(torch.nn.Module): | ||
| 129 | + def __init__(self): | ||
| 130 | + super(Model, self).__init__() | ||
| 131 | + | ||
| 132 | + def forward(self, data1, data2, gamma): | ||
| 133 | + x_out = torch.add(data1, data2) | ||
| 134 | + y, _ = torch_npu.npu_rms_norm(x_out, gamma) | ||
| 135 | + | ||
| 136 | + abs_01 = torch.abs(y) | ||
| 137 | + sqrt_01 = torch.sqrt(x_out) | ||
| 138 | + return abs_01, sqrt_01 | ||
| 139 | + | ||
| 140 | + model = Model().npu() | ||
| 141 | + | ||
| 142 | + x1 = torch.randn(1, 1, 7168, dtype=torch.float16, device='npu') | ||
| 143 | + x2 = torch.randn(1, 1, 7168, dtype=torch.float16, device='npu') | ||
| 144 | + gamma = torch.ones(7168, dtype=torch.float16, device='npu') | ||
| 145 | + | ||
| 146 | + model_compile = torch.compile(model, backend="npugraph_ex", fullgraph=True, dynamic=False) | ||
| 147 | + res = model_compile(x1, x2, gamma) | ||
| 148 | + self.assertEqual(res[0].shape[2], 7168) | ||
| 149 | + self.assertEqual(res[1].shape[2], 7168) | ||
| 150 | + | ||
| 151 | + def test_multi_stream(self): | ||
| 152 | + class Model(torch.nn.Module): | ||
| 153 | + def __init__(self): | ||
| 154 | + super().__init__() | ||
| 155 | + self.tagged_event1 = torch.npu.npugraph_ex.ops.npu_create_tagged_event(tag="66") | ||
| 156 | + self.tagged_event2 = torch.npu.npugraph_ex.ops.npu_create_tagged_event(tag="77") | ||
| 157 | + | ||
| 158 | + def forward(self, in1, in2, in3, in4): | ||
| 159 | + add_result = torch.add(in1, in2) | ||
| 160 | + torch.npu.npugraph_ex.ops.npu_tagged_event_record(self.tagged_event1) | ||
| 161 | + with torch.npu.npugraph_ex.scope.npu_stream_switch('1'): | ||
| 162 | + torch.npu.npugraph_ex.ops.npu_tagged_event_wait(self.tagged_event1) | ||
| 163 | + mm_result = torch.mm(in3, in4) | ||
| 164 | + torch.npu.npugraph_ex.ops.npu_tagged_event_record(self.tagged_event2) | ||
| 165 | + B = in3 + in4 | ||
| 166 | + torch.npu.npugraph_ex.ops.npu_record_tagged_stream(B, '1') | ||
| 167 | + mm1 = torch.mm(in3, in4) | ||
| 168 | + del B | ||
| 169 | + C = torch.ones(1000, 1000, dtype=torch.float16, device="npu") | ||
| 170 | + C.add_(2) | ||
| 171 | + with torch.npu.npugraph_ex.scope.npu_stream_switch('2'): | ||
| 172 | + torch.npu.npugraph_ex.ops.npu_tagged_event_wait(self.tagged_event2) | ||
| 173 | + add2 = torch.add(in3, in4) | ||
| 174 | + return add_result, mm_result, mm1, add2, C | ||
| 175 | + | ||
| 176 | + model = Model().npu() | ||
| 177 | + model = torch.compile(model, backend="npugraph_ex", fullgraph=True, dynamic=False) | ||
| 178 | + in1 = torch.randn(1000, 1000, dtype=torch.float16, device="npu") | ||
| 179 | + in2 = torch.randn(1000, 1000, dtype=torch.float16, device="npu") | ||
| 180 | + in3 = torch.randn(1000, 1000, dtype=torch.float16, device="npu") | ||
| 181 | + in4 = torch.randn(1000, 1000, dtype=torch.float16, device="npu") | ||
| 182 | + res = model(in1, in2, in3, in4) | ||
| 183 | + self.assertEqual(res[0].shape[0], 1000) | ||
| 184 | + self.assertEqual(res[1].shape[0], 1000) | ||
| 185 | + self.assertEqual(res[2].shape[0], 1000) | ||
| 186 | + self.assertEqual(res[3].shape[0], 1000) | ||
| 187 | + self.assertEqual(res[4].shape[0], 1000) | ||
| 188 | + | ||
| 189 | + def test_record_and_wait(self): | ||
| 190 | + def demo(x): | ||
| 191 | + with torch.npu.npugraph_ex.scope.npu_stream_switch('1'): | ||
| 192 | + mm = torch.mm(x, x) | ||
| 193 | + res = torch.abs(mm) | ||
| 194 | + record = torch.npu.npugraph_ex.ops.record() | ||
| 195 | + add = torch.add(res, 1) | ||
| 196 | + torch.npu.npugraph_ex.ops.wait([record]) | ||
| 197 | + sub = torch.sub(x, mm) | ||
| 198 | + return add, sub | ||
| 199 | + | ||
| 200 | + func = torch.compile(demo, backend="npugraph_ex", fullgraph=True, dynamic=False) | ||
| 201 | + input1 = torch.ones(2, 2).npu() | ||
| 202 | + res = func(input1) | ||
| 203 | + self.assertTrue(same(res[0], torch.empty(2, 2).fill_(2).npu())) | ||
| 204 | + self.assertTrue(same(res[1], torch.empty(2, 2).fill_(-1).npu())) | ||
| 205 | + | ||
| 206 | + | ||
| 207 | +if __name__ == "__main__": | ||
| 208 | + from torch._dynamo.test_case import run_tests | ||
| 209 | + | ||
| 210 | + run_tests() | ||
| @@ -2255,12 +2255,12 @@ | |||
| 2255 | "torch_npu.npu.npugraph_ex.ops.npu_tagged_event_record": { | 2255 | "torch_npu.npu.npugraph_ex.ops.npu_tagged_event_record": { |
| 2256 | "signature": "(event)" | 2256 | "signature": "(event)" |
| 2257 | }, | 2257 | }, |
| 2258 | - "torch_npu.npu.npugraph_ex.ops.npu_print": { | ||
| 2259 | - "signature": "(*args, summarize_size=3, tensor_detail=False)" | ||
| 2260 | - }, | ||
| 2261 | "torch_npu.npu.npugraph_ex.ops.npu_tagged_event_wait": { | 2258 | "torch_npu.npu.npugraph_ex.ops.npu_tagged_event_wait": { |
| 2262 | "signature": "(event)" | 2259 | "signature": "(event)" |
| 2263 | }, | 2260 | }, |
| 2261 | + "torch_npu.npu.npugraph_ex.ops.record": { | ||
| 2262 | + "signature": "" | ||
| 2263 | + }, | ||
| 2264 | "torch_npu.npu.npugraph_ex.ops.wait": { | 2264 | "torch_npu.npu.npugraph_ex.ops.wait": { |
| 2265 | "signature": "(tensors: list)" | 2265 | "signature": "(tensors: list)" |
| 2266 | }, | 2266 | }, |
| @@ -2273,6 +2273,12 @@ | |||
| 2273 | "torch_npu.npu.npugraph_ex.scope.limit_core_num": { | 2273 | "torch_npu.npu.npugraph_ex.scope.limit_core_num": { |
| 2274 | "signature": "(op_aicore_num: int, op_vectorcore_num: int)" | 2274 | "signature": "(op_aicore_num: int, op_vectorcore_num: int)" |
| 2275 | }, | 2275 | }, |
| 2276 | + "torch_npu.npu.npugraph_ex.compile_fx": { | ||
| 2277 | + "signature": "(options: dict = None)" | ||
| 2278 | + }, | ||
| 2279 | + "torch_npu.npu.npugraph_ex.register_replacement": { | ||
| 2280 | + "signature": "(search_fn, replace_fn, example_inputs, trace_fn=<function fwd_only>, extra_check=<function _return_true>, search_fn_pattern=None)" | ||
| 2281 | + }, | ||
| 2276 | "torch_npu.distributed.run.parse_args": { | 2282 | "torch_npu.distributed.run.parse_args": { |
| 2277 | "signature": "(args)" | 2283 | "signature": "(args)" |
| 2278 | }, | 2284 | }, |
| @@ -1,4 +1,35 @@ | |||
| 1 | +__all__ = ["compile_fx", "register_replacement"] | ||
| 2 | + | ||
| 3 | +from typing import Match | ||
| 4 | + | ||
| 5 | +try: | ||
| 6 | + from torch._inductor.pattern_matcher import fwd_only | ||
| 7 | +except ImportError: | ||
| 8 | + from torch._inductor.pattern_matcher import inference_graph as fwd_only | ||
| 9 | + | ||
| 1 | from . import experimental | 10 | from . import experimental |
| 2 | from . import inference | 11 | from . import inference |
| 3 | from . import ops | 12 | from . import ops |
| 4 | -from . import scope | 13 | +from . import scope |
| 14 | + | ||
| 15 | + | ||
| 16 | +def compile_fx(options: dict = None): | ||
| 17 | + import torchair | ||
| 18 | + from torchair.configs import npugraphex_config | ||
| 19 | + | ||
| 20 | + compiler_config = torchair.CompilerConfig() | ||
| 21 | + compiler_config.mode = "npugraph_ex" | ||
| 22 | + npugraphex_config._process_kwargs_options(compiler_config, {options: options}) | ||
| 23 | + return torchair.get_compiler(npugraphex_config) | ||
| 24 | + | ||
| 25 | + | ||
| 26 | +def _return_true(match: Match): | ||
| 27 | + return True | ||
| 28 | + | ||
| 29 | + | ||
| 30 | +def register_replacement(search_fn, replace_fn, example_inputs, trace_fn=fwd_only, extra_check=_return_true, | ||
| 31 | + search_fn_pattern=None): | ||
| 32 | + import torchair | ||
| 33 | + return torchair.patterns.pattern_pass_manager.register_replacement(search_fn, replace_fn, example_inputs, | ||
| 34 | + trace_fn=trace_fn, extra_check=extra_check, | ||
| 35 | + search_fn_pattern=search_fn_pattern) | ||
| @@ -1,11 +1,6 @@ | |||
| 1 | import torch | 1 | import torch |
| 2 | 2 | ||
| 3 | 3 | ||
| 4 | -def npu_print(*args, summarize_size=3, tensor_detail=False): | ||
| 5 | - from torch_npu.dynamo.torchair import ops | ||
| 6 | - return ops.npu_print(*args, summarize_size=summarize_size, tensor_detail=tensor_detail) | ||
| 7 | - | ||
| 8 | - | ||
| 9 | def npu_create_tagged_event(tag: str): | 4 | def npu_create_tagged_event(tag: str): |
| 10 | from torch_npu.dynamo.torchair import ops | 5 | from torch_npu.dynamo.torchair import ops |
| 11 | return ops.npu_create_tagged_event(tag=tag) | 6 | return ops.npu_create_tagged_event(tag=tag) |
| @@ -26,6 +21,11 @@ def npu_record_tagged_stream(input: torch.Tensor, tagged_stream: str): | |||
| 26 | return ops.npu_record_tagged_stream(input=input, tagged_stream=tagged_stream) | 21 | return ops.npu_record_tagged_stream(input=input, tagged_stream=tagged_stream) |
| 27 | 22 | ||
| 28 | 23 | ||
| 24 | +def record(): | ||
| 25 | + from torch_npu.dynamo.torchair import ops | ||
| 26 | + return ops.record() | ||
| 27 | + | ||
| 28 | + | ||
| 29 | def wait(tensors: list): | 29 | def wait(tensors: list): |
| 30 | from torch_npu.dynamo.torchair import ops | 30 | from torch_npu.dynamo.torchair import ops |
| 31 | return ops.wait(tensors=tensors) | 31 | return ops.wait(tensors=tensors) |