1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
|
# SPDX-FileCopyrightText: Copyright 2022, Arm Limited and/or its affiliates.
# SPDX-License-Identifier: Apache-2.0
"""Vela operator compatibility module."""
from __future__ import annotations
import itertools
import logging
from dataclasses import dataclass
from pathlib import Path
from ethosu.vela.operation import Op
from ethosu.vela.tflite_mapping import optype_to_builtintype
from ethosu.vela.tflite_model_semantic import TFLiteSemantic
from ethosu.vela.tflite_supported_operators import TFLiteSupportedOperators
from ethosu.vela.vela import generate_supported_ops
from mlia.backend.vela.compiler import VelaCompiler
from mlia.backend.vela.compiler import VelaCompilerOptions
from mlia.utils.logging import redirect_output
logger = logging.getLogger(__name__)
VELA_INTERNAL_OPS = (Op.Placeholder, Op.SubgraphInput, Op.Const)
@dataclass
class NpuSupported:
"""Operator's npu supported attribute."""
supported: bool
reasons: list[tuple[str, str]]
@dataclass
class Operator:
"""Model operator."""
name: str
op_type: str
run_on_npu: NpuSupported
@property
def cpu_only(self) -> bool:
"""Return true if operator is CPU only."""
cpu_only_reasons = [("CPU only operator", "")]
return (
not self.run_on_npu.supported
and self.run_on_npu.reasons == cpu_only_reasons
)
@dataclass
class Operators:
"""Model's operators."""
ops: list[Operator]
@property
def npu_supported_ratio(self) -> float:
"""Return NPU supported ratio."""
total = self.total_number
npu_supported = self.npu_supported_number
if total == 0 or npu_supported == 0:
return 0
return npu_supported / total
@property
def npu_unsupported_ratio(self) -> float:
"""Return NPU unsupported ratio."""
return 1 - self.npu_supported_ratio
@property
def total_number(self) -> int:
"""Return total number of operators."""
return len(self.ops)
@property
def npu_supported_number(self) -> int:
"""Return number of npu supported operators."""
return sum(op.run_on_npu.supported for op in self.ops)
def supported_operators(
model_path: Path, compiler_options: VelaCompilerOptions
) -> Operators:
"""Return list of model's operators."""
logger.debug("Check supported operators for the model %s", model_path)
vela_compiler = VelaCompiler(compiler_options)
initial_model = vela_compiler.read_model(model_path)
return Operators(
[
Operator(op.name, optype_to_builtintype(op.type), run_on_npu(op))
for sg in initial_model.nng.subgraphs
for op in sg.get_all_ops()
if op.type not in VELA_INTERNAL_OPS
]
)
def run_on_npu(operator: Op) -> NpuSupported:
"""Return information if operator can run on NPU.
Vela does a number of checks that can help establish whether
a particular operator is supported to run on NPU.
There are two groups of checks:
- general TensorFlow Lite constraints
- operator specific constraints
If an operator is not supported on NPU then this function
will return the reason of that.
The reason is split in two parts:
- general description of why the operator cannot be placed on NPU
- details on the particular operator
"""
semantic_checker = TFLiteSemantic()
semantic_constraints = itertools.chain(
semantic_checker.generic_constraints,
semantic_checker.specific_constraints[operator.type],
)
for constraint in semantic_constraints:
op_valid, op_reason = constraint(operator)
if not op_valid:
return NpuSupported(False, [(constraint.__doc__, op_reason)])
if operator.type not in TFLiteSupportedOperators.supported_operators:
reasons = (
[("CPU only operator", "")]
if operator.type not in VELA_INTERNAL_OPS
else []
)
return NpuSupported(False, reasons)
tflite_supported_operators = TFLiteSupportedOperators()
operation_constraints = itertools.chain(
tflite_supported_operators.generic_constraints,
tflite_supported_operators.specific_constraints[operator.type],
)
for constraint in operation_constraints:
op_valid, op_reason = constraint(operator)
if not op_valid:
return NpuSupported(False, [(constraint.__doc__, op_reason)])
return NpuSupported(True, [])
def generate_supported_operators_report() -> None:
"""Generate supported operators report in current working directory."""
with redirect_output(logger):
generate_supported_ops()
|