Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion backends/openvino/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@
from .preprocess import OpenvinoBackend
from .quantizer.quantizer import OpenVINOQuantizer

__all__ = [OpenvinoBackend, OpenvinoPartitioner, OpenVINOQuantizer]
__all__ = [OpenvinoBackend, OpenvinoPartitioner, OpenVINOQuantizer]
4 changes: 3 additions & 1 deletion backends/openvino/preprocess.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,9 @@ def preprocess(
for spec in module_compile_spec:
compile_options[spec.key] = spec.value.decode()

compiled = openvino_compile(edge_program.module(), *args, options=compile_options)
compiled = openvino_compile(
edge_program.module(), *args, options=compile_options
)
model_bytes = compiled.export_model()

return PreprocessResult(processed_bytes=model_bytes)
75 changes: 52 additions & 23 deletions backends/openvino/quantizer/quantizer.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,20 +8,21 @@
from enum import Enum
from typing import Dict, List, Optional, Tuple

import torch.fx
from torch.ao.quantization.observer import HistogramObserver
from torch.ao.quantization.observer import PerChannelMinMaxObserver
from torch.ao.quantization.quantizer.quantizer import EdgeOrNode
from torch.ao.quantization.quantizer.quantizer import QuantizationAnnotation
from torch.ao.quantization.quantizer.quantizer import QuantizationSpec
from torch.ao.quantization.quantizer.quantizer import QuantizationSpecBase
from torch.ao.quantization.quantizer.quantizer import Quantizer
from torch.ao.quantization.quantizer.quantizer import SharedQuantizationSpec

import nncf
import nncf.common.quantization as quantization
import nncf.experimental.torch.fx as nncf_fx

import torch.fx
from nncf.common.graph.graph import NNCFGraph
from torch.ao.quantization.observer import HistogramObserver, PerChannelMinMaxObserver
from torch.ao.quantization.quantizer.quantizer import (
EdgeOrNode,
QuantizationAnnotation,
QuantizationSpec,
QuantizationSpecBase,
Quantizer,
SharedQuantizationSpec,
)

QUANT_ANNOTATION_KEY = "quantization_annotation"

Expand Down Expand Up @@ -69,8 +70,10 @@ def __init__(
else:
preset = None
model_type = nncf.parameters.ModelType.TRANSFORMER
self._min_max_algo = nncf.quantization.algorithms.min_max.algorithm.MinMaxQuantization(
preset=preset, model_type=model_type, **kwargs
self._min_max_algo = (
nncf.quantization.algorithms.min_max.algorithm.MinMaxQuantization(
preset=preset, model_type=model_type, **kwargs
)
)

def set_ignored_scope(
Expand Down Expand Up @@ -129,8 +132,14 @@ def annotate(self, model: torch.fx.GraphModule) -> torch.fx.GraphModule:
)
root_qp = quantization_setup.quantization_points[root_quantizer_id]

if any(root_qp.qconfig != quantization_setup.quantization_points[q_id].qconfig for q_id in quantizer_ids):
qps = [quantization_setup.quantization_points[q_id] for q_id in quantizer_ids]
if any(
root_qp.qconfig != quantization_setup.quantization_points[q_id].qconfig
for q_id in quantizer_ids
):
qps = [
quantization_setup.quantization_points[q_id]
for q_id in quantizer_ids
]
msg = (
"Different quantization configs are set to one unified scale group:"
f"{[(qp.insertion_point.__dict__, str(qp.qconfig)) for qp in qps]}"
Expand All @@ -140,7 +149,9 @@ def annotate(self, model: torch.fx.GraphModule) -> torch.fx.GraphModule:
root_target_node = nncf_fx.node_utils.get_graph_node_by_name(
graph, root_qp.insertion_point.target_node_name
)
root_edge_or_node = self._get_edge_or_node(root_target_node, root_qp, nncf_graph)
root_edge_or_node = self._get_edge_or_node(
root_target_node, root_qp, nncf_graph
)

for quantizer_id in quantizer_ids:
if quantizer_id == root_quantizer_id:
Expand Down Expand Up @@ -177,9 +188,14 @@ def _get_unified_scales_root_quantizer_id(
nncf_node_quantizer_id = None
root_quantizer_id = None
for quantizer_id in quantizer_ids:
target_node_name = quantizer_setup.quantization_points[quantizer_id].insertion_point.target_node_name
target_node_name = quantizer_setup.quantization_points[
quantizer_id
].insertion_point.target_node_name
nncf_node = nncf_graph.get_node_by_name(target_node_name)
if nncf_node_quantizer_id is None or nncf_node.node_id < nncf_node_quantizer_id:
if (
nncf_node_quantizer_id is None
or nncf_node.node_id < nncf_node_quantizer_id
):
root_quantizer_id = quantizer_id
nncf_node_quantizer_id = nncf_node.node_id
return root_quantizer_id
Expand All @@ -202,14 +218,18 @@ def _get_edge_or_node_and_annotation(
QuantizationAnnotations.
:return: A tuple containing the EdgeOrNode and its associated QuantizationAnnotation.
"""
target_node = nncf_fx.node_utils.get_graph_node_by_name(graph, qp.insertion_point.target_node_name)
target_node = nncf_fx.node_utils.get_graph_node_by_name(
graph, qp.insertion_point.target_node_name
)
annotation = node_vs_torch_annotation[target_node]
edge_or_node = OpenVINOQuantizer._get_edge_or_node(target_node, qp, nncf_graph)
return edge_or_node, annotation

@staticmethod
def _get_edge_or_node(
target_node: torch.fx.Node, qp: quantization.quantizer_setup.QuantizationPointBase, nncf_graph: NNCFGraph
target_node: torch.fx.Node,
qp: quantization.quantizer_setup.QuantizationPointBase,
nncf_graph: NNCFGraph,
) -> EdgeOrNode:
"""
Returns the edge or node based on the given target node and quantization point.
Expand All @@ -222,7 +242,11 @@ def _get_edge_or_node(
ip = qp.insertion_point
if qp.is_weight_quantization_point():
nncf_node = nncf_graph.get_node_by_name(target_node.name)
weights_ports_ids = nncf.torch.model_graph_manager.get_weight_tensor_port_ids(nncf_node, nncf_graph)
weights_ports_ids = (
nncf.torch.model_graph_manager.get_weight_tensor_port_ids(
nncf_node, nncf_graph
)
)
if len(weights_ports_ids) > 1:
# TODO(dlyakhov): support quantization for nodes with several weights
nncf.common.logging.nncf_logger.warning(
Expand Down Expand Up @@ -259,7 +283,9 @@ def _fill_torch_ao_annotation(
annotation_to_update.input_qspec_map[edge_or_node[0]] = qspec

@staticmethod
def _get_torch_ao_qspec_from_qp(qp: quantization.quantizer_setup.QuantizationPointBase) -> QuantizationSpec:
def _get_torch_ao_qspec_from_qp(
qp: quantization.quantizer_setup.QuantizationPointBase,
) -> QuantizationSpec:
"""
Retrieves the quantization configuration from the given quantization point and
converts it into a QuantizationSpec.
Expand Down Expand Up @@ -293,7 +319,8 @@ def _get_torch_ao_qspec_from_qp(qp: quantization.quantizer_setup.QuantizationPoi
else:
observer = (
HistogramObserver
if torch_qscheme in [torch.per_tensor_symmetric, torch.per_tensor_affine]
if torch_qscheme
in [torch.per_tensor_symmetric, torch.per_tensor_affine]
else PerChannelMinMaxObserver
)
quant_min = 0
Expand All @@ -313,6 +340,8 @@ def _get_torch_ao_qspec_from_qp(qp: quantization.quantizer_setup.QuantizationPoi
def validate(self, model: torch.fx.GraphModule) -> None:
pass

def transform_for_annotation(self, model: torch.fx.GraphModule) -> torch.fx.GraphModule:
def transform_for_annotation(
self, model: torch.fx.GraphModule
) -> torch.fx.GraphModule:
nncf_fx.transformations.fold_constant_except_qdq(model)
return model
18 changes: 11 additions & 7 deletions backends/openvino/tests/models/test_classification.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
from executorch.backends.openvino.tests.ops.base_openvino_op_test import BaseOpenvinoOpTest
import torch
import timm
import torch
import torchvision.models as torchvision_models
from executorch.backends.openvino.tests.ops.base_openvino_op_test import (
BaseOpenvinoOpTest,
)
from transformers import AutoModel

classifier_params = [
{'model': ['torchvision', 'resnet50', (1, 3, 224, 224)] },
{'model': ['torchvision', 'mobilenet_v2', (1, 3, 224, 224)] },
]
{"model": ["torchvision", "resnet50", (1, 3, 224, 224)]},
{"model": ["torchvision", "mobilenet_v2", (1, 3, 224, 224)]},
]


# Function to load a model based on the selected suite
def load_model(suite: str, model_name: str):
Expand All @@ -22,13 +25,14 @@ def load_model(suite: str, model_name: str):
else:
raise ValueError(f"Unsupported model suite: {suite}")


class TestClassifier(BaseOpenvinoOpTest):

def test_classifier(self):
for params in classifier_params:
with self.subTest(params=params):
module = load_model(params['model'][0], params['model'][1])
module = load_model(params["model"][0], params["model"][1])

sample_input = (torch.randn(params['model'][2]),)
sample_input = (torch.randn(params["model"][2]),)

self.execute_layer_test(module, sample_input)
41 changes: 28 additions & 13 deletions backends/openvino/tests/ops/base_openvino_op_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,15 @@
import tempfile
import unittest

import executorch

import numpy as np
import torch
import executorch
from executorch.backends.openvino.partitioner import OpenvinoPartitioner
from executorch.backends.openvino.preprocess import OpenvinoBackend
from executorch.exir import EdgeProgramManager, to_edge
from executorch.exir.backend.backend_details import CompileSpec
from torch.export import export, ExportedProgram
from executorch.exir import EdgeProgramManager, to_edge
from executorch.backends.openvino.preprocess import OpenvinoBackend


class BaseOpenvinoOpTest(unittest.TestCase):
Expand Down Expand Up @@ -41,7 +42,9 @@ def execute_layer_test(
lowered_module = edge_program.to_backend(OpenvinoPartitioner(compile_spec))

# Apply backend-specific passes
exec_prog = lowered_module.to_executorch(config=executorch.exir.ExecutorchBackendConfig())
exec_prog = lowered_module.to_executorch(
config=executorch.exir.ExecutorchBackendConfig()
)

# Check if the number of partitions created matches the expected number of partitions
self.assertEqual(
Expand All @@ -56,7 +59,7 @@ def execute_layer_test(
)

# Execute the model and compare the outputs with the reference outputs
if (assert_output_equal):
if assert_output_equal:
with tempfile.TemporaryDirectory() as tmp_dir:
input_list = ""
for idx, _ in enumerate(sample_inputs):
Expand All @@ -69,15 +72,19 @@ def execute_layer_test(
# Execute the module in eager mode to calculate the reference outputs
ref_output = module(*sample_inputs)
if isinstance(ref_output, torch.Tensor):
ref_output = [ref_output,]
ref_output = [
ref_output,
]

# Serialize the executorch model and save into a temporary file
pte_fname = f"{tmp_dir}/openvino_executorch_test.pte"
with open(pte_fname, "wb") as file:
exec_prog.write_to_file(file)

# Save inputs into a temporary file
self.generate_inputs(tmp_dir, "input_list.txt", [sample_inputs], input_list)
self.generate_inputs(
tmp_dir, "input_list.txt", [sample_inputs], input_list
)
self.make_output_dir(output_dir)

# Start a subprocess to execute model with openvino_executor_runner
Expand Down Expand Up @@ -108,7 +115,9 @@ def execute_layer_test(

for i, f in enumerate(sorted(os.listdir(output_dir))):
filename = os.path.join(output_dir, f)
output = np.fromfile(filename, dtype=ref_output[i].detach().numpy().dtype)
output = np.fromfile(
filename, dtype=ref_output[i].detach().numpy().dtype
)
output = torch.from_numpy(output).reshape(ref_output[i].shape)
outputs.append(output)

Expand All @@ -117,30 +126,36 @@ def execute_layer_test(
for i in range(len(ref_output)):
self.assertTrue(
torch.allclose(
outputs[i], ref_output[i], atol=self.atol, rtol=self.rtol, equal_nan=True
outputs[i],
ref_output[i],
atol=self.atol,
rtol=self.rtol,
equal_nan=True,
),
msg=f"ref_output:\n{ref_output[i]}\n\ntest_output:\n{outputs[i]}",
)

def generate_inputs(self, dest_path: str, file_name: str, inputs=None, input_list=None):
def generate_inputs(
self, dest_path: str, file_name: str, inputs=None, input_list=None
):
input_list_file = None
input_files = []

# Prepare input list
if input_list is not None:
input_list_file = f"{dest_path}/{file_name}"
with open(input_list_file, "w") as f:
f.write(input_list)
f.flush()

# Prepare input data
if inputs is not None:
for idx, data in enumerate(inputs):
for i, d in enumerate(data):
file_name = f"{dest_path}/input_{idx}_{i}.raw"
d.detach().numpy().tofile(file_name)
input_files.append(file_name)

return input_list_file, input_files

def make_output_dir(self, path: str):
Expand Down
7 changes: 5 additions & 2 deletions backends/openvino/tests/ops/test_add.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
from executorch.backends.openvino.tests.ops.base_openvino_op_test import BaseOpenvinoOpTest
import torch
from executorch.backends.openvino.tests.ops.base_openvino_op_test import (
BaseOpenvinoOpTest,
)


class TestAddOperator(BaseOpenvinoOpTest):

def create_model(self):
class Add(torch.nn.Module):
def __init__(self):
super().__init__()

def forward(self, x, y):
return torch.add(x, y)

Expand Down
19 changes: 11 additions & 8 deletions backends/openvino/tests/ops/test_addmm.py
Original file line number Diff line number Diff line change
@@ -1,25 +1,28 @@
from executorch.backends.openvino.tests.ops.base_openvino_op_test import BaseOpenvinoOpTest
import torch
from executorch.backends.openvino.tests.ops.base_openvino_op_test import (
BaseOpenvinoOpTest,
)


class TestAddMMOperator(BaseOpenvinoOpTest):

def create_model(self):
class AddMM(torch.nn.Module):
def __init__(self):
super().__init__()
self.alpha = 1.
self.beta = 1.
self.alpha = 1.0
self.beta = 1.0

def forward(self, x, y, z):
#return torch.add(x, y)
# return torch.add(x, y)
return torch.addmm(x, y, z, alpha=self.alpha, beta=self.beta)

return AddMM()

def test_addmm(self):
module = self.create_model()
input_x = torch.randn(4,4, dtype=torch.float32)
input_y = torch.randn(4,4, dtype=torch.float32)
input_z = torch.randn(4,4, dtype=torch.float32)
input_x = torch.randn(4, 4, dtype=torch.float32)
input_y = torch.randn(4, 4, dtype=torch.float32)
input_z = torch.randn(4, 4, dtype=torch.float32)
sample_input = (input_x, input_y, input_z)
self.execute_layer_test(module, sample_input)
7 changes: 5 additions & 2 deletions backends/openvino/tests/ops/test_arange.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
from executorch.backends.openvino.tests.ops.base_openvino_op_test import BaseOpenvinoOpTest
import torch
from executorch.backends.openvino.tests.ops.base_openvino_op_test import (
BaseOpenvinoOpTest,
)


class TestArangeOperator(BaseOpenvinoOpTest):

Expand All @@ -8,7 +11,7 @@ class Arange(torch.nn.Module):
def __init__(self, x):
super().__init__()
self.x = x

def forward(self, y):
return torch.arange(self.x, dtype=torch.float32) + y

Expand Down
Loading