This is an automated email from the ASF dual-hosted git repository.

tqchen pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/tvm.git


The following commit(s) were added to refs/heads/main by this push:
     new 0e9c683035 [Relax][PyTorch] Add support for 
`torch.nn.functional.conv*` (#17325)
0e9c683035 is described below

commit 0e9c68303543e9b7e7a0146553aa0e81f63828f4
Author: Masahiro Hiramori <[email protected]>
AuthorDate: Wed Sep 4 02:39:57 2024 +0900

    [Relax][PyTorch] Add support for `torch.nn.functional.conv*` (#17325)
    
    * add test for functional conv1d
    
    * add support for functional conv1d
    
    * cleanup conv1d
    
    * add test for functional conv_transpose1d
    
    * add support for functional conv_transpose1d
    
    * add test for functional conv_transpose2d
    
    * add support for functional conv_transpose2d
    
    * add test for functional conv3d
    
    * add support for functional conv3d
---
 python/tvm/relax/frontend/torch/fx_translator.py | 284 ++++++++++++++++++-----
 tests/python/relax/test_frontend_from_fx.py      |  52 +++++
 2 files changed, 275 insertions(+), 61 deletions(-)

diff --git a/python/tvm/relax/frontend/torch/fx_translator.py 
b/python/tvm/relax/frontend/torch/fx_translator.py
index 676f63b5c3..245bb4cffb 100644
--- a/python/tvm/relax/frontend/torch/fx_translator.py
+++ b/python/tvm/relax/frontend/torch/fx_translator.py
@@ -740,61 +740,140 @@ class TorchFXImporter:
         bias = args[2] if len(args) > 2 else None
         return self.block_builder.emit(relax.op.linear(x, weight, bias, 
"float32"))
 
-    def _conv1d(self, node: fx.node.Node) -> relax.Var:
-        x = self.env[node.args[0]]
-        module = self.named_modules[node.target]
-        weight = self.params[module.weight]
-
+    def _conv1d_impl(
+        self,
+        x: relax.Expr,
+        weight: relax.Expr,
+        bias: Optional[relax.Expr],
+        strides: Optional[Tuple],
+        padding: Optional[Tuple],
+        dilation: Optional[Tuple],
+        groups: Optional[Tuple],
+    ) -> relax.Var:
         conv1d = self.block_builder.emit(
             relax.op.nn.conv1d(
                 x,
                 weight,
-                strides=module.stride,
-                padding=module.padding,
-                dilation=module.dilation,
-                groups=module.groups,
+                strides=strides,
+                padding=padding,
+                dilation=dilation,
+                groups=groups,
                 data_layout="NCW",
                 kernel_layout="OIW",
                 out_dtype="float32",
             )
         )
 
-        if module.bias is None:
+        if bias is None:
             return conv1d
-
-        bias = self.params[module.bias]
         assert len(self.shape_of(bias)) == 1
         bias = relax.op.reshape(bias, (1, -1, 1))
-
         return self.block_builder.emit(relax.op.add(conv1d, bias))
 
-    def _conv3d(self, node: fx.node.Node) -> relax.Var:
+    def _conv1d(self, node: fx.node.Node) -> relax.Var:
         x = self.env[node.args[0]]
         module = self.named_modules[node.target]
         weight = self.params[module.weight]
+        bias = None
+        if module.bias is not None:
+            bias = self.params[module.bias]
 
-        conv3d = self.block_builder.emit(
-            relax.op.nn.conv3d(
+        return self._conv1d_impl(
+            x,
+            weight,
+            bias=bias,
+            strides=module.stride,
+            padding=module.padding,
+            dilation=module.dilation,
+            groups=module.groups,
+        )
+
+    def _conv1d_functional(self, node: fx.node.Node) -> relax.Var:
+        args = self.retrieve_args(node)
+        x = args[0]
+        weight = args[1]
+        bias = args[2] if len(args) > 2 else None
+        stride = args[3] if len(args) > 3 else 1
+        padding = args[4] if len(args) > 4 else 0
+        dilation = args[5] if len(args) > 5 else 1
+        groups = args[6] if len(args) > 6 else 1
+        return self._conv1d_impl(
+            x,
+            weight,
+            bias=bias,
+            strides=stride,
+            padding=padding,
+            dilation=dilation,
+            groups=groups,
+        )
+
+    def _conv1d_transpose_impl(
+        self,
+        x: relax.Expr,
+        weight: relax.Expr,
+        bias: Optional[relax.Expr],
+        strides: Optional[Tuple],
+        padding: Optional[Tuple],
+        dilation: Optional[Tuple],
+        groups: Optional[Tuple],
+    ) -> relax.Var:
+        conv1d_transpose = self.block_builder.emit(
+            relax.op.nn.conv1d_transpose(
                 x,
                 weight,
-                strides=module.stride,
-                padding=module.padding,
-                dilation=module.dilation,
-                groups=module.groups,
-                data_layout="NCDHW",
-                kernel_layout="OIDHW",
+                strides=strides,
+                padding=padding,
+                dilation=dilation,
+                groups=groups,
+                data_layout="NCW",
+                kernel_layout="OIW",
                 out_dtype="float32",
             )
         )
 
-        if module.bias is None:
-            return conv3d
+        if bias is None:
+            return conv1d_transpose
 
-        bias = self.params[module.bias]
         assert len(self.shape_of(bias)) == 1
-        bias = relax.op.reshape(bias, (1, -1, 1, 1, 1))
+        bias = relax.op.reshape(bias, (1, -1, 1))
+        return self.block_builder.emit(relax.op.add(conv1d_transpose, bias))
 
-        return self.block_builder.emit(relax.op.add(conv3d, bias))
+    def _conv1d_transpose(self, node: fx.node.Node) -> relax.Var:
+        x = self.env[node.args[0]]
+        module = self.named_modules[node.target]
+        weight = self.params[module.weight]
+        bias = None
+        if module.bias is not None:
+            bias = self.params[module.bias]
+
+        return self._conv1d_transpose_impl(
+            x,
+            weight,
+            bias=bias,
+            strides=module.stride,
+            padding=module.padding,
+            dilation=module.dilation,
+            groups=module.groups,
+        )
+
+    def _conv1d_transpose_functional(self, node: fx.node.Node) -> relax.Var:
+        args = self.retrieve_args(node)
+        x = args[0]
+        weight = args[1]
+        bias = args[2] if len(args) > 2 else None
+        stride = args[3] if len(args) > 3 else 1
+        padding = args[4] if len(args) > 4 else 0
+        dilation = args[5] if len(args) > 5 else 1
+        groups = args[6] if len(args) > 6 else 1
+        return self._conv1d_transpose_impl(
+            x,
+            weight,
+            bias=bias,
+            strides=stride,
+            padding=padding,
+            dilation=dilation,
+            groups=groups,
+        )
 
     def _conv2d_impl(
         self,
@@ -826,63 +905,142 @@ class TorchFXImporter:
         bias = relax.op.reshape(bias, (1, -1, 1, 1))
         return self.block_builder.emit(relax.op.add(conv2d, bias))
 
-    def _conv1d_transpose(self, node: fx.node.Node) -> relax.Var:
+    def _conv2d(self, node: fx.node.Node) -> relax.Var:
         x = self.env[node.args[0]]
         module = self.named_modules[node.target]
         weight = self.params[module.weight]
+        bias = None
+        if module.bias is not None:
+            bias = self.params[module.bias]
 
-        conv1d_transpose = self.block_builder.emit(
-            relax.op.nn.conv1d_transpose(
+        return self._conv2d_impl(
+            x,
+            weight,
+            bias=bias,
+            strides=module.stride,
+            padding=module.padding,
+            dilation=module.dilation,
+            groups=module.groups,
+        )
+
+    def _conv2d_functional(self, node: fx.node.Node) -> relax.Var:
+        args = self.retrieve_args(node)
+        x = args[0]
+        weight = args[1]
+        bias = args[2] if len(args) > 2 else None
+        stride = args[3] if len(args) > 3 else 1
+        padding = args[4] if len(args) > 4 else 0
+        dilation = args[5] if len(args) > 5 else 1
+        groups = args[6] if len(args) > 6 else 1
+        return self._conv2d_impl(
+            x,
+            weight,
+            bias=bias,
+            strides=stride,
+            padding=padding,
+            dilation=dilation,
+            groups=groups,
+        )
+
+    def _conv2d_transpose_impl(
+        self,
+        x: relax.Expr,
+        weight: relax.Expr,
+        bias: Optional[relax.Expr],
+        strides: Optional[Tuple],
+        padding: Optional[Tuple],
+        dilation: Optional[Tuple],
+        groups: Optional[Tuple],
+    ) -> relax.Var:
+        conv2d_transpose = self.block_builder.emit(
+            relax.op.nn.conv2d_transpose(
                 x,
                 weight,
-                strides=module.stride,
-                padding=module.padding,
-                dilation=module.dilation,
-                groups=module.groups,
-                data_layout="NCW",
-                kernel_layout="OIW",
+                strides=strides,
+                padding=padding,
+                dilation=dilation,
+                groups=groups,
+                data_layout="NCHW",
+                kernel_layout="OIHW",
                 out_dtype="float32",
             )
         )
 
-        if module.bias is None:
-            return conv1d_transpose
+        if bias is None:
+            return conv2d_transpose
 
-        bias = self.params[module.bias]
         assert len(self.shape_of(bias)) == 1
-        bias = relax.op.reshape(bias, (1, -1, 1))
-
-        return self.block_builder.emit(relax.op.add(conv1d_transpose, bias))
+        bias = relax.op.reshape(bias, (1, -1, 1, 1))
+        return self.block_builder.emit(relax.op.add(conv2d_transpose, bias))
 
     def _conv2d_transpose(self, node: fx.node.Node) -> relax.Var:
         x = self.env[node.args[0]]
         module = self.named_modules[node.target]
         weight = self.params[module.weight]
+        bias = None
+        if module.bias is not None:
+            bias = self.params[module.bias]
 
-        conv2d_transpose = self.block_builder.emit(
-            relax.op.nn.conv2d_transpose(
+        return self._conv2d_transpose_impl(
+            x,
+            weight,
+            bias=bias,
+            strides=module.stride,
+            padding=module.padding,
+            dilation=module.dilation,
+            groups=module.groups,
+        )
+
+    def _conv2d_transpose_functional(self, node: fx.node.Node) -> relax.Var:
+        args = self.retrieve_args(node)
+        x = args[0]
+        weight = args[1]
+        bias = args[2] if len(args) > 2 else None
+        stride = args[3] if len(args) > 3 else 1
+        padding = args[4] if len(args) > 4 else 0
+        dilation = args[5] if len(args) > 5 else 1
+        groups = args[6] if len(args) > 6 else 1
+        return self._conv2d_transpose_impl(
+            x,
+            weight,
+            bias=bias,
+            strides=stride,
+            padding=padding,
+            dilation=dilation,
+            groups=groups,
+        )
+
+    def _conv3d_impl(
+        self,
+        x: relax.Expr,
+        weight: relax.Expr,
+        bias: Optional[relax.Expr],
+        strides: Optional[Tuple],
+        padding: Optional[Tuple],
+        dilation: Optional[Tuple],
+        groups: Optional[Tuple],
+    ):
+        conv3d = self.block_builder.emit(
+            relax.op.nn.conv3d(
                 x,
                 weight,
-                strides=module.stride,
-                padding=module.padding,
-                dilation=module.dilation,
-                groups=module.groups,
-                data_layout="NCHW",
-                kernel_layout="OIHW",
+                strides=strides,
+                padding=padding,
+                dilation=dilation,
+                groups=groups,
+                data_layout="NCDHW",
+                kernel_layout="OIDHW",
                 out_dtype="float32",
             )
         )
 
-        if module.bias is None:
-            return conv2d_transpose
-
-        bias = self.params[module.bias]
+        if bias is None:
+            return conv3d
         assert len(self.shape_of(bias)) == 1
-        bias = relax.op.reshape(bias, (1, -1, 1, 1))
-
-        return self.block_builder.emit(relax.op.add(conv2d_transpose, bias))
+        bias = relax.op.reshape(bias, (1, -1, 1, 1, 1))
+        return self.block_builder.emit(relax.op.add(conv3d, bias))
 
-    def _conv2d(self, node: fx.node.Node) -> relax.Var:
+    def _conv3d(self, node: fx.node.Node) -> relax.Var:
         x = self.env[node.args[0]]
         module = self.named_modules[node.target]
         weight = self.params[module.weight]
@@ -890,7 +1048,7 @@ class TorchFXImporter:
         if module.bias is not None:
             bias = self.params[module.bias]
 
-        return self._conv2d_impl(
+        return self._conv3d_impl(
             x,
             weight,
             bias=bias,
@@ -900,7 +1058,7 @@ class TorchFXImporter:
             groups=module.groups,
         )
 
-    def _conv2d_functional(self, node: fx.node.Node) -> relax.Var:
+    def _conv3d_functional(self, node: fx.node.Node) -> relax.Var:
         args = self.retrieve_args(node)
         x = args[0]
         weight = args[1]
@@ -909,7 +1067,7 @@ class TorchFXImporter:
         padding = args[4] if len(args) > 4 else 0
         dilation = args[5] if len(args) > 5 else 1
         groups = args[6] if len(args) > 6 else 1
-        return self._conv2d_impl(
+        return self._conv3d_impl(
             x,
             weight,
             bias=bias,
@@ -1482,7 +1640,11 @@ class TorchFXImporter:
             "type": self._type,
             "astype": self._type,
             "matmul": self._matmul,
+            "conv1d": self._conv1d_functional,
+            "conv_transpose1d": self._conv1d_transpose_functional,
             "conv2d": self._conv2d_functional,
+            "conv_transpose2d": self._conv2d_transpose_functional,
+            "conv3d": self._conv3d_functional,
             "linear": self._linear_functional,
             "addmm": self._addmm,
             "baddbmm": self._baddbmm,
diff --git a/tests/python/relax/test_frontend_from_fx.py 
b/tests/python/relax/test_frontend_from_fx.py
index c6c4f25972..e191775a63 100644
--- a/tests/python/relax/test_frontend_from_fx.py
+++ b/tests/python/relax/test_frontend_from_fx.py
@@ -48,6 +48,15 @@ def test_conv1d():
         def forward(self, input):
             return self.conv(input)
 
+    class Conv1D1Func(Module):
+        def __init__(self):
+            super().__init__()
+            self.weight = torch.randn(size=[6, 3, 7])
+            self.bias = torch.randn(size=[6])
+
+        def forward(self, input):
+            return torch.nn.functional.conv1d(input, self.weight, self.bias)
+
     @tvm.script.ir_module
     class expected1:
         @R.function
@@ -113,6 +122,10 @@ def test_conv1d():
     binding = {"w1": model.conv.weight.detach().numpy(), "w2": 
model.conv.bias.detach().numpy()}
     verify_model(model, input_info, binding, expected1)
 
+    model = Conv1D1Func()
+    binding = {"w1": model.weight.detach().numpy(), "w2": 
model.bias.detach().numpy()}
+    verify_model(model, input_info, binding, expected1)
+
     model = Conv1D2()
     binding = {"w1": model.conv.weight.detach().numpy()}
     verify_model(model, input_info, binding, expected2)
@@ -127,6 +140,15 @@ def test_conv1d_transpose():
         def forward(self, input):
             return self.conv(input)
 
+    class ConvTranspose1d1Func(Module):
+        def __init__(self):
+            super().__init__()
+            self.weight = torch.randn(size=[6, 6, 3])
+            self.bias = torch.randn(size=[6])
+
+        def forward(self, input):
+            return torch.nn.functional.conv_transpose1d(input, self.weight, 
self.bias)
+
     @tvm.script.ir_module
     class expected1:
         @R.function
@@ -192,6 +214,10 @@ def test_conv1d_transpose():
     binding = {"w1": model.conv.weight.detach().numpy(), "w2": 
model.conv.bias.detach().numpy()}
     verify_model(model, input_info, binding, expected1)
 
+    model = ConvTranspose1d1Func()
+    binding = {"w1": model.weight.detach().numpy(), "w2": 
model.bias.detach().numpy()}
+    verify_model(model, input_info, binding, expected1)
+
     model = ConvTranspose1d2()
     binding = {"w1": model.conv.weight.detach().numpy()}
     verify_model(model, input_info, binding, expected2)
@@ -298,6 +324,15 @@ def test_conv2d_transpose():
         def forward(self, input):
             return self.conv(input)
 
+    class ConvTranspose2d1Func(Module):
+        def __init__(self):
+            super().__init__()
+            self.weight = torch.randn(size=[3, 3, 7, 7])
+            self.bias = torch.randn(size=[3])
+
+        def forward(self, input):
+            return torch.nn.functional.conv_transpose2d(input, self.weight, 
self.bias)
+
     @tvm.script.ir_module
     class expected1:
         @R.function
@@ -363,6 +398,10 @@ def test_conv2d_transpose():
     binding = {"w1": model.conv.weight.detach().numpy(), "w2": 
model.conv.bias.detach().numpy()}
     verify_model(model, input_info, binding, expected1)
 
+    model = ConvTranspose2d1Func()
+    binding = {"w1": model.weight.detach().numpy(), "w2": 
model.bias.detach().numpy()}
+    verify_model(model, input_info, binding, expected1)
+
     model = ConvTranspose2d2()
     binding = {"w1": model.conv.weight.detach().numpy()}
     verify_model(model, input_info, binding, expected2)
@@ -377,6 +416,15 @@ def test_conv3d():
         def forward(self, input):
             return self.conv(input)
 
+    class Conv3D1Func(Module):
+        def __init__(self):
+            super().__init__()
+            self.weight = torch.randn(size=[6, 3, 7, 7, 7])
+            self.bias = torch.randn(size=[6])
+
+        def forward(self, input):
+            return torch.nn.functional.conv3d(input, self.weight, self.bias)
+
     @tvm.script.ir_module
     class expected1:
         @R.function
@@ -442,6 +490,10 @@ def test_conv3d():
     binding = {"w1": model.conv.weight.detach().numpy(), "w2": 
model.conv.bias.detach().numpy()}
     verify_model(model, input_info, binding, expected1)
 
+    model = Conv3D1Func()
+    binding = {"w1": model.weight.detach().numpy(), "w2": 
model.bias.detach().numpy()}
+    verify_model(model, input_info, binding, expected1)
+
     model = Conv3D2()
     binding = {"w1": model.conv.weight.detach().numpy()}
     verify_model(model, input_info, binding, expected2)

Reply via email to