We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
MCT fails for "dims" parameter of type List in torch.permute
### Issue Type

Bug

### Source

source

### MCT Version

nightly

### OS Platform and Distribution

_No response_

### Python version

_No response_

### Describe the issue

```shell
the dims parameter is a list which fails MCT
x = torch.permute(x, [0, 2, 3, 1])
switching to a tuple works fine
x = torch.permute(x, (0, 2, 3, 1))
```


### Expected behaviour

_No response_

### Code to reproduce the issue

```shell
import torch
import torch.nn as nn
import torch.nn.functional as F
import model_compression_toolkit as mct


class BugNet(nn.Module):
    def __init__(self):
        super().__init__()
        self.conv = nn.Conv2d(3, 6, 5)
        self.fc = nn.Linear(216, 2)

    def forward(self, x):
        x = F.relu(self.conv(x))
        x = torch.permute(x, [0, 2, 3, 1])
        x = torch.reshape(x, [-1, 216])
        x = self.fc(x)
        return x


def rep_dataset():
    yield [torch.zeros(16, 3, 10, 10)]


model = BugNet()

# forward pass works
model(torch.zeros(16, 3, 10, 10))

mct.ptq.pytorch_post_training_quantization_experimental(model, rep_dataset)
```


### Log output

```shell
File ".../model_optimization/model_compression_toolkit/core/pytorch/back2framework/pytorch_model_builder.py", line 98, in _run_operation
    out_tensors_of_n_float = op_func(*input_tensors + op_call_args, **functional_kwargs)
TypeError: permute(): argument 'dims' (position 2) must be tuple of ints, but found element of type immutable_list at pos 0
```


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `model_compression_toolkit/core/pytorch/reader/graph_builders.py`
Content:
```
1 # Copyright 2022 Sony Semiconductor Israel, Inc. All rights reserved.
2 #
3 # Licensed under the Apache License, Version 2.0 (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 #     http://www.apache.org/licenses/LICENSE-2.0
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 import inspect
16 from typing import Dict, List, Tuple, Callable
17 import torch
18 from torch.fx import GraphModule, Node
19 
20 from model_compression_toolkit.core import common
21 from model_compression_toolkit.core.common import BaseNode
22 from model_compression_toolkit.core.common.graph.base_graph import OutTensor
23 from model_compression_toolkit.core.common.graph.edge import Edge
24 from model_compression_toolkit.core.common.graph.functional_node import FunctionalNode
25 from model_compression_toolkit.core.pytorch.constants import OUTPUT, PLACEHOLDER, TENSOR_META, CALL_FUNCTION, TYPE, \
26     CALL_METHOD, BIAS, FUNCTIONAL_OP, OP_CALL_KWARGS, OP_CALL_ARGS, INPUTS_AS_LIST, TENSOR_INPUT_INDICES, GET_ATTR
27 from model_compression_toolkit.core.pytorch.reader.node_holders import DummyPlaceHolder
28 from model_compression_toolkit.logger import Logger
29 
30 
31 def extract_holder_weights(constant_name, node_target, model, weights, to_numpy):
32     """
33     Extract layer weights and named buffers to a dictionary.
34     Args:
35         constant_name: name to write the parameters under, should be the node name.
36         node_target: relevant parameter name from Pytorch FX model.
37         model: Pytorch FX model.
38         weights: dictionary containing the weights of the node.
39         to_numpy: Function to convert framework's tensor to a Numpy array.
40 
41     Returns:
42         Updated weights dictionary.
43     """
44     named_parameters_weights = {constant_name: to_numpy(parameter) for name, parameter in
45                                 model.named_parameters() if node_target == name}
46     named_buffer_weights = {constant_name: to_numpy(parameter) for name, parameter in
47                             model.named_buffers() if node_target == name}
48     if len(named_parameters_weights) + len(named_buffer_weights) > 1:
49         Logger.critical("A single constant parameter must correspond to exactly one tensor. Found {len(named_parameters_weights) + len(named_buffer_weights)} parameters.")
50 
51     weights.update(named_parameters_weights)
52     weights.update(named_buffer_weights)
53     return weights
54 
55 
56 def nodes_builder(model: GraphModule,
57                   module_dict: Dict,
58                   to_numpy: Callable) -> Tuple[List, List, List, Dict]:
59     """
60     Build a node from a fx node. A node contains all information to reconstruct the model module or call function
61     it's representing in the model: operation, module configuration, weights, input/output shape.
62     Args:
63         model: Pytorch FX model.
64         module_dict: A dictionary of the Pyotrch model's named modules.
65         to_numpy: A function to convert a Tensor to numpy array
66 
67     Returns:
68         A list of Graph nodes that were built from the fx GraphModule nodes.
69     """
70     # init function variables:
71     inputs = []
72     outputs = []
73     nodes = []
74     output_nodes = []
75     fx_node_2_graph_node = {}
76     consts_dict = {}
77     used_consts = set()
78 
79     for node in model.graph.nodes:
80         # extract node type and framework attributes
81         framework_attr = dict(node.kwargs)
82         node_has_activation = True
83         if node.target in module_dict.keys():
84             node_module = module_dict[node.target]
85             node_type = type(node_module)
86             framework_attr = node_module.__dict__
87             fullargspec = inspect.getfullargspec(node_type.__init__).args
88             framework_attr = {k: v for k, v in framework_attr.items() if k in fullargspec}
89             if hasattr(node_module, BIAS) and BIAS in fullargspec:
90                 framework_attr[BIAS] = False if node_module.bias is None else True
91         elif node.op == CALL_FUNCTION:
92             node_type = node.target
93             if node_type == getattr:
94                 node_has_activation = False
95                 Logger.warning(
96                     'Pytorch model has a parameter or constant Tensor value. This can cause unexpected behaviour when '
97                     'converting the model.')
98         elif node.op == PLACEHOLDER:
99             node_type = DummyPlaceHolder
100         elif node.op == OUTPUT:
101             output_nodes += node.all_input_nodes
102             continue
103         elif node.op == CALL_METHOD:
104             if hasattr(torch, node.target):
105                 node_type = getattr(torch, node.target)
106             elif hasattr(torch.Tensor, node.target):
107                 node_type = getattr(torch.Tensor, node.target)
108             else:
109                 Logger.critical(f"The call method '{node.target}' is not supported.")
110         elif node.op == GET_ATTR:
111             Logger.warning(
112                 'Pytorch model has a parameter or constant Tensor value. This can cause unexpected behaviour when '
113                 'converting the model.')
114         else:
115             Logger.critical(f'Encountered an unsupported node type in node: {node.name}.')
116 
117         # extract layer weights and named buffers
118         weights = {}
119         if node.target in module_dict.keys():
120             named_parameters_weights = {name: to_numpy(parameter) for name, parameter in
121                                         module_dict[node.target].named_parameters()}
122             named_buffer_weights = {name: to_numpy(parameter) for name, parameter in
123                                     module_dict[node.target].named_buffers() if len(parameter.shape) > 0}
124             weights.update(named_parameters_weights)
125             weights.update(named_buffer_weights)
126 
127         if node.op == GET_ATTR:
128             new_const = extract_holder_weights(node, node.target, model, weights, to_numpy)
129             if list(new_const.keys())[0] in consts_dict:
130                 Logger.critical('A constant weight appears to have been recorded multiple times.')
131             consts_dict.update(new_const)
132             continue
133 
134         # extract input shapes and const weights
135         input_shape = []
136         if node.op != PLACEHOLDER:
137             for i, input_node in enumerate(node.all_input_nodes):
138                 if input_node in consts_dict:
139                     used_consts.add(input_node)
140                     weights.update({i: consts_dict[input_node]})
141                 tensor_meta = input_node.meta
142                 if tensor_meta[TYPE] == torch.Tensor:
143                     input_shape += [list(tensor_meta[TENSOR_META].shape)]
144                 elif tensor_meta[TYPE] == tuple:
145                     input_shape += [list(n.shape) for n in tensor_meta[TENSOR_META]]
146                 elif tensor_meta[TYPE] == int:
147                     input_shape += [[1]]
148 
149         # extract output shapes
150         if node.meta[TYPE] == torch.Tensor:
151             output_shape = [list(node.meta[TENSOR_META].shape)]
152         elif node.meta[TYPE] in (list, tuple):
153             output_shape = [list(m.shape) for m in node.meta[TENSOR_META]]
154         elif node.meta[TYPE] == int:
155             output_shape = [[1]]
156         else:
157             output_shape = []
158 
159         # filter Nodes from framework attributes, we replace these attributes with nx graph nodes
160         framework_attr_filtered = {}
161         for k, v in framework_attr.items():
162             if not isinstance(v, torch.fx.node.Node):
163                 framework_attr_filtered[k] = v
164         framework_attr = framework_attr_filtered
165 
166         # filter Nodes from node kwargs, we replace these attributes with nx graph nodes
167         node_kwargs = {}
168         for k, v in node.kwargs.items():
169             if not isinstance(v, torch.fx.node.Node):
170                 node_kwargs[k] = v
171 
172         # initiate graph nodes
173         if node.op in [CALL_METHOD, CALL_FUNCTION]:
174             graph_node_type = FunctionalNode
175             inputs_as_list1 = len(node.args) > 0 and isinstance(node.args[0], (list, tuple)) and all(
176                 [isinstance(n, torch.fx.node.Node) for n in node.args[0]])
177             inputs_as_list = inputs_as_list1 or (len(node.args) > 0 and isinstance(node.args[0], Node) and
178                                                  node.args[0].op == PLACEHOLDER and node.args[0].meta[TYPE] in (list, tuple))
179             tensor_input_index = []
180             op_call_args = list(node.args)
181             if inputs_as_list:
182                 op_call_args.pop(0)
183             else:
184                 for in_node in node.all_input_nodes:
185                     for i, arg in enumerate(node.args):
186                         if arg == in_node:
187                             tensor_input_index.append(i)
188 
189             # remove torch.fx.node.Node from inputs to graph_node_type
190             op_call_args = [arg for arg in op_call_args if not isinstance(arg, Node)]
191 
192             kwargs = {FUNCTIONAL_OP: node_type,
193                       OP_CALL_ARGS: op_call_args,
194                       OP_CALL_KWARGS: node_kwargs,
195                       INPUTS_AS_LIST: inputs_as_list,
196                       TENSOR_INPUT_INDICES: tensor_input_index}
197         else:
198             graph_node_type = BaseNode
199             kwargs = {}
200         graph_node = graph_node_type(name=node.name,
201                                      framework_attr=framework_attr,
202                                      input_shape=input_shape,
203                                      output_shape=output_shape,
204                                      weights=weights,
205                                      layer_class=node_type,
206                                      has_activation=node_has_activation,
207                                      **kwargs)
208 
209         # generate graph inputs list
210         if node.op == PLACEHOLDER:
211             for ii in range(len(output_shape)):
212                 inputs.append(graph_node)
213 
214         fx_node_2_graph_node[node] = graph_node
215         nodes.append(graph_node)
216 
217     # make sure all extracted constants were used in the graph
218     not_connected_consts = [c for c in consts_dict if c not in used_consts]
219     if not_connected_consts:
220         Logger.critical(f'Error reading graph: These constants are not connected in the graph: {not_connected_consts}.')
221 
222     # generate graph outputs list
223     for node in output_nodes:
224         outputs.append(OutTensor(fx_node_2_graph_node[node], output_nodes.index(node)))
225 
226     return nodes, inputs, outputs, fx_node_2_graph_node
227 
228 
229 def edges_builder(model: GraphModule,
230                   fx_node_2_graph_node: Dict) -> List:
231     """
232 
233     Args:
234         model: Pytorch FX model.
235         fx_node_2_graph_node: dictionary from fx node to graph node.
236 
237     Returns:
238         List of graph edges
239     """
240     src_index = 0  # in fx src_index is always zero because fx uses the getitem operator to fetch node outputs
241     edges = []
242     connectivity_dict = {}
243     for node in model.graph.nodes:
244         if node.op != OUTPUT:
245             for input_node in node.all_input_nodes:
246                 if input_node in fx_node_2_graph_node:
247                     # n_edges_for_input_node is for the case that the input node appears more than
248                     # once as the input of the node, for example add(x, x)
249                     n_edges_for_input_node = sum([1 for a in node.args if input_node == a])
250                     n_edges_for_input_node = max(n_edges_for_input_node, 1)
251 
252                     dst_index = node.all_input_nodes.index(input_node)
253                     for i in range(n_edges_for_input_node):
254                         if connectivity_dict.get(input_node):
255                             connectivity_dict[input_node].append((node, dst_index))
256                         else:
257                             connectivity_dict[input_node] = [(node, dst_index)]
258                         dst_index += 1
259     for node in model.graph.nodes:
260         out_nodes = connectivity_dict.get(node)
261         if out_nodes:
262             for (out_node, dst_index) in out_nodes:
263                 edges.append(
264                     Edge(fx_node_2_graph_node[node], fx_node_2_graph_node[out_node], src_index, dst_index))
265 
266     return edges
267
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/model_compression_toolkit/core/pytorch/reader/graph_builders.py b/model_compression_toolkit/core/pytorch/reader/graph_builders.py
--- a/model_compression_toolkit/core/pytorch/reader/graph_builders.py
+++ b/model_compression_toolkit/core/pytorch/reader/graph_builders.py
@@ -188,6 +188,9 @@
 
             # remove torch.fx.node.Node from inputs to graph_node_type
             op_call_args = [arg for arg in op_call_args if not isinstance(arg, Node)]
+            # convert torch.fx.immutable_collections.immutable_list to tuple
+            op_call_args = [tuple(arg) if isinstance(arg, torch.fx.immutable_collections.immutable_list) else arg
+                            for arg in op_call_args]
 
             kwargs = {FUNCTIONAL_OP: node_type,
                       OP_CALL_ARGS: op_call_args,

{"golden_diff": "diff --git a/model_compression_toolkit/core/pytorch/reader/graph_builders.py b/model_compression_toolkit/core/pytorch/reader/graph_builders.py\n--- a/model_compression_toolkit/core/pytorch/reader/graph_builders.py\n+++ b/model_compression_toolkit/core/pytorch/reader/graph_builders.py\n@@ -188,6 +188,9 @@\n \n             # remove torch.fx.node.Node from inputs to graph_node_type\n             op_call_args = [arg for arg in op_call_args if not isinstance(arg, Node)]\n+            # convert torch.fx.immutable_collections.immutable_list to tuple\n+            op_call_args = [tuple(arg) if isinstance(arg, torch.fx.immutable_collections.immutable_list) else arg\n+                            for arg in op_call_args]\n \n             kwargs = {FUNCTIONAL_OP: node_type,\n                       OP_CALL_ARGS: op_call_args,\n", "issue": "MCT fails for \"dims\" parameter of type List in torch.permute\n### Issue Type\n\nBug\n\n### Source\n\nsource\n\n### MCT Version\n\nnightly\n\n### OS Platform and Distribution\n\n_No response_\n\n### Python version\n\n_No response_\n\n### Describe the issue\n\n```shell\nthe dims parameter is a list which fails MCT\r\nx = torch.permute(x, [0, 2, 3, 1])\r\nswitching to a tuple works fine\r\nx = torch.permute(x, (0, 2, 3, 1))\n```\n\n\n### Expected behaviour\n\n_No response_\n\n### Code to reproduce the issue\n\n```shell\nimport torch\r\nimport torch.nn as nn\r\nimport torch.nn.functional as F\r\nimport model_compression_toolkit as mct\r\n\r\n\r\nclass BugNet(nn.Module):\r\n    def __init__(self):\r\n        super().__init__()\r\n        self.conv = nn.Conv2d(3, 6, 5)\r\n        self.fc = nn.Linear(216, 2)\r\n\r\n    def forward(self, x):\r\n        x = F.relu(self.conv(x))\r\n        x = torch.permute(x, [0, 2, 3, 1])\r\n        x = torch.reshape(x, [-1, 216])\r\n        x = self.fc(x)\r\n        return x\r\n\r\n\r\ndef rep_dataset():\r\n    yield [torch.zeros(16, 3, 10, 10)]\r\n\r\n\r\nmodel = BugNet()\r\n\r\n# forward pass works\r\nmodel(torch.zeros(16, 3, 10, 10))\r\n\r\nmct.ptq.pytorch_post_training_quantization_experimental(model, rep_dataset)\n```\n\n\n### Log output\n\n```shell\nFile \".../model_optimization/model_compression_toolkit/core/pytorch/back2framework/pytorch_model_builder.py\", line 98, in _run_operation\r\n    out_tensors_of_n_float = op_func(*input_tensors + op_call_args, **functional_kwargs)\r\nTypeError: permute(): argument 'dims' (position 2) must be tuple of ints, but found element of type immutable_list at pos 0\n```\n\n", "before_files": [{"content": "# Copyright 2022 Sony Semiconductor Israel, Inc. All rights reserved.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n# ==============================================================================\nimport inspect\nfrom typing import Dict, List, Tuple, Callable\nimport torch\nfrom torch.fx import GraphModule, Node\n\nfrom model_compression_toolkit.core import common\nfrom model_compression_toolkit.core.common import BaseNode\nfrom model_compression_toolkit.core.common.graph.base_graph import OutTensor\nfrom model_compression_toolkit.core.common.graph.edge import Edge\nfrom model_compression_toolkit.core.common.graph.functional_node import FunctionalNode\nfrom model_compression_toolkit.core.pytorch.constants import OUTPUT, PLACEHOLDER, TENSOR_META, CALL_FUNCTION, TYPE, \\\n    CALL_METHOD, BIAS, FUNCTIONAL_OP, OP_CALL_KWARGS, OP_CALL_ARGS, INPUTS_AS_LIST, TENSOR_INPUT_INDICES, GET_ATTR\nfrom model_compression_toolkit.core.pytorch.reader.node_holders import DummyPlaceHolder\nfrom model_compression_toolkit.logger import Logger\n\n\ndef extract_holder_weights(constant_name, node_target, model, weights, to_numpy):\n    \"\"\"\n    Extract layer weights and named buffers to a dictionary.\n    Args:\n        constant_name: name to write the parameters under, should be the node name.\n        node_target: relevant parameter name from Pytorch FX model.\n        model: Pytorch FX model.\n        weights: dictionary containing the weights of the node.\n        to_numpy: Function to convert framework's tensor to a Numpy array.\n\n    Returns:\n        Updated weights dictionary.\n    \"\"\"\n    named_parameters_weights = {constant_name: to_numpy(parameter) for name, parameter in\n                                model.named_parameters() if node_target == name}\n    named_buffer_weights = {constant_name: to_numpy(parameter) for name, parameter in\n                            model.named_buffers() if node_target == name}\n    if len(named_parameters_weights) + len(named_buffer_weights) > 1:\n        Logger.critical(\"A single constant parameter must correspond to exactly one tensor. Found {len(named_parameters_weights) + len(named_buffer_weights)} parameters.\")\n\n    weights.update(named_parameters_weights)\n    weights.update(named_buffer_weights)\n    return weights\n\n\ndef nodes_builder(model: GraphModule,\n                  module_dict: Dict,\n                  to_numpy: Callable) -> Tuple[List, List, List, Dict]:\n    \"\"\"\n    Build a node from a fx node. A node contains all information to reconstruct the model module or call function\n    it's representing in the model: operation, module configuration, weights, input/output shape.\n    Args:\n        model: Pytorch FX model.\n        module_dict: A dictionary of the Pyotrch model's named modules.\n        to_numpy: A function to convert a Tensor to numpy array\n\n    Returns:\n        A list of Graph nodes that were built from the fx GraphModule nodes.\n    \"\"\"\n    # init function variables:\n    inputs = []\n    outputs = []\n    nodes = []\n    output_nodes = []\n    fx_node_2_graph_node = {}\n    consts_dict = {}\n    used_consts = set()\n\n    for node in model.graph.nodes:\n        # extract node type and framework attributes\n        framework_attr = dict(node.kwargs)\n        node_has_activation = True\n        if node.target in module_dict.keys():\n            node_module = module_dict[node.target]\n            node_type = type(node_module)\n            framework_attr = node_module.__dict__\n            fullargspec = inspect.getfullargspec(node_type.__init__).args\n            framework_attr = {k: v for k, v in framework_attr.items() if k in fullargspec}\n            if hasattr(node_module, BIAS) and BIAS in fullargspec:\n                framework_attr[BIAS] = False if node_module.bias is None else True\n        elif node.op == CALL_FUNCTION:\n            node_type = node.target\n            if node_type == getattr:\n                node_has_activation = False\n                Logger.warning(\n                    'Pytorch model has a parameter or constant Tensor value. This can cause unexpected behaviour when '\n                    'converting the model.')\n        elif node.op == PLACEHOLDER:\n            node_type = DummyPlaceHolder\n        elif node.op == OUTPUT:\n            output_nodes += node.all_input_nodes\n            continue\n        elif node.op == CALL_METHOD:\n            if hasattr(torch, node.target):\n                node_type = getattr(torch, node.target)\n            elif hasattr(torch.Tensor, node.target):\n                node_type = getattr(torch.Tensor, node.target)\n            else:\n                Logger.critical(f\"The call method '{node.target}' is not supported.\")\n        elif node.op == GET_ATTR:\n            Logger.warning(\n                'Pytorch model has a parameter or constant Tensor value. This can cause unexpected behaviour when '\n                'converting the model.')\n        else:\n            Logger.critical(f'Encountered an unsupported node type in node: {node.name}.')\n\n        # extract layer weights and named buffers\n        weights = {}\n        if node.target in module_dict.keys():\n            named_parameters_weights = {name: to_numpy(parameter) for name, parameter in\n                                        module_dict[node.target].named_parameters()}\n            named_buffer_weights = {name: to_numpy(parameter) for name, parameter in\n                                    module_dict[node.target].named_buffers() if len(parameter.shape) > 0}\n            weights.update(named_parameters_weights)\n            weights.update(named_buffer_weights)\n\n        if node.op == GET_ATTR:\n            new_const = extract_holder_weights(node, node.target, model, weights, to_numpy)\n            if list(new_const.keys())[0] in consts_dict:\n                Logger.critical('A constant weight appears to have been recorded multiple times.')\n            consts_dict.update(new_const)\n            continue\n\n        # extract input shapes and const weights\n        input_shape = []\n        if node.op != PLACEHOLDER:\n            for i, input_node in enumerate(node.all_input_nodes):\n                if input_node in consts_dict:\n                    used_consts.add(input_node)\n                    weights.update({i: consts_dict[input_node]})\n                tensor_meta = input_node.meta\n                if tensor_meta[TYPE] == torch.Tensor:\n                    input_shape += [list(tensor_meta[TENSOR_META].shape)]\n                elif tensor_meta[TYPE] == tuple:\n                    input_shape += [list(n.shape) for n in tensor_meta[TENSOR_META]]\n                elif tensor_meta[TYPE] == int:\n                    input_shape += [[1]]\n\n        # extract output shapes\n        if node.meta[TYPE] == torch.Tensor:\n            output_shape = [list(node.meta[TENSOR_META].shape)]\n        elif node.meta[TYPE] in (list, tuple):\n            output_shape = [list(m.shape) for m in node.meta[TENSOR_META]]\n        elif node.meta[TYPE] == int:\n            output_shape = [[1]]\n        else:\n            output_shape = []\n\n        # filter Nodes from framework attributes, we replace these attributes with nx graph nodes\n        framework_attr_filtered = {}\n        for k, v in framework_attr.items():\n            if not isinstance(v, torch.fx.node.Node):\n                framework_attr_filtered[k] = v\n        framework_attr = framework_attr_filtered\n\n        # filter Nodes from node kwargs, we replace these attributes with nx graph nodes\n        node_kwargs = {}\n        for k, v in node.kwargs.items():\n            if not isinstance(v, torch.fx.node.Node):\n                node_kwargs[k] = v\n\n        # initiate graph nodes\n        if node.op in [CALL_METHOD, CALL_FUNCTION]:\n            graph_node_type = FunctionalNode\n            inputs_as_list1 = len(node.args) > 0 and isinstance(node.args[0], (list, tuple)) and all(\n                [isinstance(n, torch.fx.node.Node) for n in node.args[0]])\n            inputs_as_list = inputs_as_list1 or (len(node.args) > 0 and isinstance(node.args[0], Node) and\n                                                 node.args[0].op == PLACEHOLDER and node.args[0].meta[TYPE] in (list, tuple))\n            tensor_input_index = []\n            op_call_args = list(node.args)\n            if inputs_as_list:\n                op_call_args.pop(0)\n            else:\n                for in_node in node.all_input_nodes:\n                    for i, arg in enumerate(node.args):\n                        if arg == in_node:\n                            tensor_input_index.append(i)\n\n            # remove torch.fx.node.Node from inputs to graph_node_type\n            op_call_args = [arg for arg in op_call_args if not isinstance(arg, Node)]\n\n            kwargs = {FUNCTIONAL_OP: node_type,\n                      OP_CALL_ARGS: op_call_args,\n                      OP_CALL_KWARGS: node_kwargs,\n                      INPUTS_AS_LIST: inputs_as_list,\n                      TENSOR_INPUT_INDICES: tensor_input_index}\n        else:\n            graph_node_type = BaseNode\n            kwargs = {}\n        graph_node = graph_node_type(name=node.name,\n                                     framework_attr=framework_attr,\n                                     input_shape=input_shape,\n                                     output_shape=output_shape,\n                                     weights=weights,\n                                     layer_class=node_type,\n                                     has_activation=node_has_activation,\n                                     **kwargs)\n\n        # generate graph inputs list\n        if node.op == PLACEHOLDER:\n            for ii in range(len(output_shape)):\n                inputs.append(graph_node)\n\n        fx_node_2_graph_node[node] = graph_node\n        nodes.append(graph_node)\n\n    # make sure all extracted constants were used in the graph\n    not_connected_consts = [c for c in consts_dict if c not in used_consts]\n    if not_connected_consts:\n        Logger.critical(f'Error reading graph: These constants are not connected in the graph: {not_connected_consts}.')\n\n    # generate graph outputs list\n    for node in output_nodes:\n        outputs.append(OutTensor(fx_node_2_graph_node[node], output_nodes.index(node)))\n\n    return nodes, inputs, outputs, fx_node_2_graph_node\n\n\ndef edges_builder(model: GraphModule,\n                  fx_node_2_graph_node: Dict) -> List:\n    \"\"\"\n\n    Args:\n        model: Pytorch FX model.\n        fx_node_2_graph_node: dictionary from fx node to graph node.\n\n    Returns:\n        List of graph edges\n    \"\"\"\n    src_index = 0  # in fx src_index is always zero because fx uses the getitem operator to fetch node outputs\n    edges = []\n    connectivity_dict = {}\n    for node in model.graph.nodes:\n        if node.op != OUTPUT:\n            for input_node in node.all_input_nodes:\n                if input_node in fx_node_2_graph_node:\n                    # n_edges_for_input_node is for the case that the input node appears more than\n                    # once as the input of the node, for example add(x, x)\n                    n_edges_for_input_node = sum([1 for a in node.args if input_node == a])\n                    n_edges_for_input_node = max(n_edges_for_input_node, 1)\n\n                    dst_index = node.all_input_nodes.index(input_node)\n                    for i in range(n_edges_for_input_node):\n                        if connectivity_dict.get(input_node):\n                            connectivity_dict[input_node].append((node, dst_index))\n                        else:\n                            connectivity_dict[input_node] = [(node, dst_index)]\n                        dst_index += 1\n    for node in model.graph.nodes:\n        out_nodes = connectivity_dict.get(node)\n        if out_nodes:\n            for (out_node, dst_index) in out_nodes:\n                edges.append(\n                    Edge(fx_node_2_graph_node[node], fx_node_2_graph_node[out_node], src_index, dst_index))\n\n    return edges\n", "path": "model_compression_toolkit/core/pytorch/reader/graph_builders.py"}], "after_files": [{"content": "# Copyright 2022 Sony Semiconductor Israel, Inc. All rights reserved.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n# ==============================================================================\nimport inspect\nfrom typing import Dict, List, Tuple, Callable\nimport torch\nfrom torch.fx import GraphModule, Node\n\nfrom model_compression_toolkit.core import common\nfrom model_compression_toolkit.core.common import BaseNode\nfrom model_compression_toolkit.core.common.graph.base_graph import OutTensor\nfrom model_compression_toolkit.core.common.graph.edge import Edge\nfrom model_compression_toolkit.core.common.graph.functional_node import FunctionalNode\nfrom model_compression_toolkit.core.pytorch.constants import OUTPUT, PLACEHOLDER, TENSOR_META, CALL_FUNCTION, TYPE, \\\n    CALL_METHOD, BIAS, FUNCTIONAL_OP, OP_CALL_KWARGS, OP_CALL_ARGS, INPUTS_AS_LIST, TENSOR_INPUT_INDICES, GET_ATTR\nfrom model_compression_toolkit.core.pytorch.reader.node_holders import DummyPlaceHolder\nfrom model_compression_toolkit.logger import Logger\n\n\ndef extract_holder_weights(constant_name, node_target, model, weights, to_numpy):\n    \"\"\"\n    Extract layer weights and named buffers to a dictionary.\n    Args:\n        constant_name: name to write the parameters under, should be the node name.\n        node_target: relevant parameter name from Pytorch FX model.\n        model: Pytorch FX model.\n        weights: dictionary containing the weights of the node.\n        to_numpy: Function to convert framework's tensor to a Numpy array.\n\n    Returns:\n        Updated weights dictionary.\n    \"\"\"\n    named_parameters_weights = {constant_name: to_numpy(parameter) for name, parameter in\n                                model.named_parameters() if node_target == name}\n    named_buffer_weights = {constant_name: to_numpy(parameter) for name, parameter in\n                            model.named_buffers() if node_target == name}\n    if len(named_parameters_weights) + len(named_buffer_weights) > 1:\n        Logger.critical(\"A single constant parameter must correspond to exactly one tensor. Found {len(named_parameters_weights) + len(named_buffer_weights)} parameters.\")\n\n    weights.update(named_parameters_weights)\n    weights.update(named_buffer_weights)\n    return weights\n\n\ndef nodes_builder(model: GraphModule,\n                  module_dict: Dict,\n                  to_numpy: Callable) -> Tuple[List, List, List, Dict]:\n    \"\"\"\n    Build a node from a fx node. A node contains all information to reconstruct the model module or call function\n    it's representing in the model: operation, module configuration, weights, input/output shape.\n    Args:\n        model: Pytorch FX model.\n        module_dict: A dictionary of the Pyotrch model's named modules.\n        to_numpy: A function to convert a Tensor to numpy array\n\n    Returns:\n        A list of Graph nodes that were built from the fx GraphModule nodes.\n    \"\"\"\n    # init function variables:\n    inputs = []\n    outputs = []\n    nodes = []\n    output_nodes = []\n    fx_node_2_graph_node = {}\n    consts_dict = {}\n    used_consts = set()\n\n    for node in model.graph.nodes:\n        # extract node type and framework attributes\n        framework_attr = dict(node.kwargs)\n        node_has_activation = True\n        if node.target in module_dict.keys():\n            node_module = module_dict[node.target]\n            node_type = type(node_module)\n            framework_attr = node_module.__dict__\n            fullargspec = inspect.getfullargspec(node_type.__init__).args\n            framework_attr = {k: v for k, v in framework_attr.items() if k in fullargspec}\n            if hasattr(node_module, BIAS) and BIAS in fullargspec:\n                framework_attr[BIAS] = False if node_module.bias is None else True\n        elif node.op == CALL_FUNCTION:\n            node_type = node.target\n            if node_type == getattr:\n                node_has_activation = False\n                Logger.warning(\n                    'Pytorch model has a parameter or constant Tensor value. This can cause unexpected behaviour when '\n                    'converting the model.')\n        elif node.op == PLACEHOLDER:\n            node_type = DummyPlaceHolder\n        elif node.op == OUTPUT:\n            output_nodes += node.all_input_nodes\n            continue\n        elif node.op == CALL_METHOD:\n            if hasattr(torch, node.target):\n                node_type = getattr(torch, node.target)\n            elif hasattr(torch.Tensor, node.target):\n                node_type = getattr(torch.Tensor, node.target)\n            else:\n                Logger.critical(f\"The call method '{node.target}' is not supported.\")\n        elif node.op == GET_ATTR:\n            Logger.warning(\n                'Pytorch model has a parameter or constant Tensor value. This can cause unexpected behaviour when '\n                'converting the model.')\n        else:\n            Logger.critical(f'Encountered an unsupported node type in node: {node.name}.')\n\n        # extract layer weights and named buffers\n        weights = {}\n        if node.target in module_dict.keys():\n            named_parameters_weights = {name: to_numpy(parameter) for name, parameter in\n                                        module_dict[node.target].named_parameters()}\n            named_buffer_weights = {name: to_numpy(parameter) for name, parameter in\n                                    module_dict[node.target].named_buffers() if len(parameter.shape) > 0}\n            weights.update(named_parameters_weights)\n            weights.update(named_buffer_weights)\n\n        if node.op == GET_ATTR:\n            new_const = extract_holder_weights(node, node.target, model, weights, to_numpy)\n            if list(new_const.keys())[0] in consts_dict:\n                Logger.critical('A constant weight appears to have been recorded multiple times.')\n            consts_dict.update(new_const)\n            continue\n\n        # extract input shapes and const weights\n        input_shape = []\n        if node.op != PLACEHOLDER:\n            for i, input_node in enumerate(node.all_input_nodes):\n                if input_node in consts_dict:\n                    used_consts.add(input_node)\n                    weights.update({i: consts_dict[input_node]})\n                tensor_meta = input_node.meta\n                if tensor_meta[TYPE] == torch.Tensor:\n                    input_shape += [list(tensor_meta[TENSOR_META].shape)]\n                elif tensor_meta[TYPE] == tuple:\n                    input_shape += [list(n.shape) for n in tensor_meta[TENSOR_META]]\n                elif tensor_meta[TYPE] == int:\n                    input_shape += [[1]]\n\n        # extract output shapes\n        if node.meta[TYPE] == torch.Tensor:\n            output_shape = [list(node.meta[TENSOR_META].shape)]\n        elif node.meta[TYPE] in (list, tuple):\n            output_shape = [list(m.shape) for m in node.meta[TENSOR_META]]\n        elif node.meta[TYPE] == int:\n            output_shape = [[1]]\n        else:\n            output_shape = []\n\n        # filter Nodes from framework attributes, we replace these attributes with nx graph nodes\n        framework_attr_filtered = {}\n        for k, v in framework_attr.items():\n            if not isinstance(v, torch.fx.node.Node):\n                framework_attr_filtered[k] = v\n        framework_attr = framework_attr_filtered\n\n        # filter Nodes from node kwargs, we replace these attributes with nx graph nodes\n        node_kwargs = {}\n        for k, v in node.kwargs.items():\n            if not isinstance(v, torch.fx.node.Node):\n                node_kwargs[k] = v\n\n        # initiate graph nodes\n        if node.op in [CALL_METHOD, CALL_FUNCTION]:\n            graph_node_type = FunctionalNode\n            inputs_as_list1 = len(node.args) > 0 and isinstance(node.args[0], (list, tuple)) and all(\n                [isinstance(n, torch.fx.node.Node) for n in node.args[0]])\n            inputs_as_list = inputs_as_list1 or (len(node.args) > 0 and isinstance(node.args[0], Node) and\n                                                 node.args[0].op == PLACEHOLDER and node.args[0].meta[TYPE] in (list, tuple))\n            tensor_input_index = []\n            op_call_args = list(node.args)\n            if inputs_as_list:\n                op_call_args.pop(0)\n            else:\n                for in_node in node.all_input_nodes:\n                    for i, arg in enumerate(node.args):\n                        if arg == in_node:\n                            tensor_input_index.append(i)\n\n            # remove torch.fx.node.Node from inputs to graph_node_type\n            op_call_args = [arg for arg in op_call_args if not isinstance(arg, Node)]\n            # convert torch.fx.immutable_collections.immutable_list to tuple\n            op_call_args = [tuple(arg) if isinstance(arg, torch.fx.immutable_collections.immutable_list) else arg\n                            for arg in op_call_args]\n\n            kwargs = {FUNCTIONAL_OP: node_type,\n                      OP_CALL_ARGS: op_call_args,\n                      OP_CALL_KWARGS: node_kwargs,\n                      INPUTS_AS_LIST: inputs_as_list,\n                      TENSOR_INPUT_INDICES: tensor_input_index}\n        else:\n            graph_node_type = BaseNode\n            kwargs = {}\n        graph_node = graph_node_type(name=node.name,\n                                     framework_attr=framework_attr,\n                                     input_shape=input_shape,\n                                     output_shape=output_shape,\n                                     weights=weights,\n                                     layer_class=node_type,\n                                     has_activation=node_has_activation,\n                                     **kwargs)\n\n        # generate graph inputs list\n        if node.op == PLACEHOLDER:\n            for ii in range(len(output_shape)):\n                inputs.append(graph_node)\n\n        fx_node_2_graph_node[node] = graph_node\n        nodes.append(graph_node)\n\n    # make sure all extracted constants were used in the graph\n    not_connected_consts = [c for c in consts_dict if c not in used_consts]\n    if not_connected_consts:\n        Logger.critical(f'Error reading graph: These constants are not connected in the graph: {not_connected_consts}.')\n\n    # generate graph outputs list\n    for node in output_nodes:\n        outputs.append(OutTensor(fx_node_2_graph_node[node], output_nodes.index(node)))\n\n    return nodes, inputs, outputs, fx_node_2_graph_node\n\n\ndef edges_builder(model: GraphModule,\n                  fx_node_2_graph_node: Dict) -> List:\n    \"\"\"\n\n    Args:\n        model: Pytorch FX model.\n        fx_node_2_graph_node: dictionary from fx node to graph node.\n\n    Returns:\n        List of graph edges\n    \"\"\"\n    src_index = 0  # in fx src_index is always zero because fx uses the getitem operator to fetch node outputs\n    edges = []\n    connectivity_dict = {}\n    for node in model.graph.nodes:\n        if node.op != OUTPUT:\n            for input_node in node.all_input_nodes:\n                if input_node in fx_node_2_graph_node:\n                    # n_edges_for_input_node is for the case that the input node appears more than\n                    # once as the input of the node, for example add(x, x)\n                    n_edges_for_input_node = sum([1 for a in node.args if input_node == a])\n                    n_edges_for_input_node = max(n_edges_for_input_node, 1)\n\n                    dst_index = node.all_input_nodes.index(input_node)\n                    for i in range(n_edges_for_input_node):\n                        if connectivity_dict.get(input_node):\n                            connectivity_dict[input_node].append((node, dst_index))\n                        else:\n                            connectivity_dict[input_node] = [(node, dst_index)]\n                        dst_index += 1\n    for node in model.graph.nodes:\n        out_nodes = connectivity_dict.get(node)\n        if out_nodes:\n            for (out_node, dst_index) in out_nodes:\n                edges.append(\n                    Edge(fx_node_2_graph_node[node], fx_node_2_graph_node[out_node], src_index, dst_index))\n\n    return edges\n", "path": "model_compression_toolkit/core/pytorch/reader/graph_builders.py"}]}