Skip to content

Commit a923c0b

Browse files
leo-blonktristan-arm
authored andcommitted
[ETHOSN] Add support for non-default Ethos(TM)-N78 configurations (apache#9386)
- Updated tvmc with new Ethos-N78 composite target. - Added additional Ethos-N78 specific configuration options. Co-authored-by: Tristan O'Connor <tristan.oconnor@arm.com> Co-authored-by: Tristan O'Connor <tristan.oconnor@arm.com>
1 parent 0f4a680 commit a923c0b

File tree

10 files changed

+262
-30
lines changed

10 files changed

+262
-30
lines changed

python/tvm/driver/tvmc/composite_target.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,8 @@
2323
import tvm.contrib.target.vitis_ai # pylint: disable=unused-import
2424

2525
from tvm.relay.op.contrib.arm_compute_lib import partition_for_arm_compute_lib
26-
from tvm.relay.op.contrib.ethosn import partition_for_ethosn
26+
from tvm.relay.op.contrib.ethosn import partition_for_ethosn77
27+
from tvm.relay.op.contrib.ethosn import partition_for_ethosn78
2728
from tvm.relay.op.contrib.cmsisnn import partition_for_cmsisnn
2829
from tvm.relay.op.contrib.ethosu import partition_for_ethosu
2930
from tvm.relay.op.contrib.bnns import partition_for_bnns
@@ -57,7 +58,11 @@
5758
},
5859
"ethos-n77": {
5960
"config_key": "relay.ext.ethos-n.options",
60-
"pass_pipeline": partition_for_ethosn,
61+
"pass_pipeline": partition_for_ethosn77,
62+
},
63+
"ethos-n78": {
64+
"config_key": "relay.ext.ethos-n.options",
65+
"pass_pipeline": partition_for_ethosn78,
6166
},
6267
"ethos-u": {
6368
"config_key": "relay.ext.ethosu.options",

python/tvm/relay/op/contrib/ethosn.py

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ def ethosn_available():
4646
return Available.SW_AND_HW if hw else Available.SW_ONLY
4747

4848

49-
def partition_for_ethosn(mod, params=None, **opts):
49+
def partition_for_ethosn77(mod, params=None, **opts):
5050
"""Partition the graph greedily offloading supported
5151
operators to Arm Ethos-N NPU.
5252
@@ -61,6 +61,49 @@ def partition_for_ethosn(mod, params=None, **opts):
6161
-------
6262
ret : annotated and partitioned module.
6363
"""
64+
if opts:
65+
tops = opts.get("tops", None)
66+
ple_ratio = opts.get("ple_ratio", None)
67+
sram_size = opts.get("sram_size", None)
68+
if tops or ple_ratio or sram_size:
69+
raise ValueError(
70+
"Setting tops, ple_ratio or sram_size has no effect when targeting Ethos(TM)-N77"
71+
)
72+
73+
if params:
74+
mod["main"] = bind_params_by_name(mod["main"], params)
75+
76+
seq = tvm.transform.Sequential(
77+
[
78+
transform.InferType(),
79+
transform.MergeComposite(pattern_table()),
80+
transform.AnnotateTarget("ethos-n"),
81+
transform.MergeCompilerRegions(),
82+
transform.PartitionGraph(),
83+
]
84+
)
85+
86+
return seq(mod)
87+
88+
89+
def partition_for_ethosn78(mod, params=None, **opts):
90+
"""Partition the graph greedily offloading supported
91+
operators to Arm Ethos(TM)-N NPU.
92+
93+
Parameters
94+
----------
95+
mod : Module
96+
The module to run passes on.
97+
params : Optional[Dict[str, NDArray]]
98+
Constant input parameters.
99+
100+
Returns
101+
-------
102+
ret : annotated and partitioned module.
103+
"""
104+
if not opts or opts.get("variant", "").lower() != "ethos-n78":
105+
raise ValueError("When targeting Ethos(TM)-N78, -variant=Ethos-N78 should be set.")
106+
64107
if params:
65108
mod["main"] = bind_params_by_name(mod["main"], params)
66109

src/relay/backend/contrib/ethosn/codegen.cc

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,20 @@ sl::TensorsAndId MakeOps(const sl::TensorAndId<sl::Operand>& op) {
195195
return ops;
196196
}
197197

198+
String MakeVariant(auto configuration) {
199+
String variant = configuration.value()->variant;
200+
// Transform variant string to lowercase for comparison
201+
std::string variant_string = variant.c_str();
202+
std::transform(variant_string.begin(), variant_string.end(), variant_string.begin(), ::tolower);
203+
std::string variant_n78 = "ethos-n78";
204+
if (variant_string == variant_n78) {
205+
String tops = configuration.value()->tops;
206+
String ple_ratio = configuration.value()->ple_ratio;
207+
variant = "Ethos-N78_" + tops + "TOPS_" + ple_ratio + "PLE_RATIO";
208+
}
209+
return variant;
210+
}
211+
198212
NetworkWithIDs ConstructNetworkVisitor::Construct(const Function& func) {
199213
// Initialise everything
200214
auto ctx = transform::PassContext::Current();
@@ -203,8 +217,9 @@ NetworkWithIDs ConstructNetworkVisitor::Construct(const Function& func) {
203217
cfg = AttrsWithDefaultValues<EthosnCompilerConfig>();
204218
}
205219
NetworkWithIDs network_with_ids;
206-
network_ = sl::CreateNetwork(sl::GetFwAndHwCapabilities(
207-
sl::EthosNVariantFromString(cfg.value()->variant.c_str()), cfg.value()->sram_size_bytes));
220+
network_ = sl::CreateNetwork(
221+
sl::GetFwAndHwCapabilities(sl::EthosNVariantFromString(MakeVariant(cfg).c_str()),
222+
static_cast<uint32_t>(std::stoul(cfg.value()->sram_size))));
208223
network_with_ids.network = network_;
209224
operand_table_.clear();
210225

@@ -614,8 +629,9 @@ EthosnError EthosnCompiler::SupportedSetup() {
614629
auto cfg = ctx->GetConfig<EthosnCompilerConfig>("relay.ext.ethos-n.options").defined()
615630
? ctx->GetConfig<EthosnCompilerConfig>("relay.ext.ethos-n.options")
616631
: AttrsWithDefaultValues<EthosnCompilerConfig>();
617-
m_Queries = std::make_unique<sl::SupportQueries>(sl::GetFwAndHwCapabilities(
618-
sl::EthosNVariantFromString(cfg.value()->variant.c_str()), cfg.value()->sram_size_bytes));
632+
m_Queries = std::make_unique<sl::SupportQueries>(
633+
sl::GetFwAndHwCapabilities(sl::EthosNVariantFromString(cfg.value()->variant.c_str()),
634+
std::stoul(cfg.value()->sram_size)));
619635
if (m_Queries == nullptr) {
620636
return EthosnError("Could not initialise Ethos-N compiler isSupported");
621637
}

src/relay/backend/contrib/ethosn/codegen_ethosn.h

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -227,7 +227,9 @@ NetworkWithIDs ConstructNetwork(const IRModule& mod, const GlobalVar& var, const
227227
/*! \brief Attributes to store the compiler options for Ethos-N */
228228
struct EthosnCompilerConfigNode : public tvm::AttrsNode<EthosnCompilerConfigNode> {
229229
String variant;
230-
int sram_size_bytes;
230+
String sram_size;
231+
String tops;
232+
String ple_ratio;
231233
bool strategy0;
232234
bool strategy1;
233235
bool strategy3;
@@ -247,9 +249,15 @@ struct EthosnCompilerConfigNode : public tvm::AttrsNode<EthosnCompilerConfigNode
247249

248250
TVM_DECLARE_ATTRS(EthosnCompilerConfigNode, "ext.attrs.EthosnCompilerConfigNode") {
249251
TVM_ATTR_FIELD(variant).describe("See Ethos-N documentation.").set_default("Ethos-N77");
250-
TVM_ATTR_FIELD(sram_size_bytes)
251-
.describe("Optionally override the default sram size. See Ethos-N documentation.")
252-
.set_default(0);
252+
TVM_ATTR_FIELD(sram_size)
253+
.describe("Optionally override the default sram size. See Ethos(TM)-N documentation.")
254+
.set_default("0");
255+
TVM_ATTR_FIELD(tops)
256+
.describe("Valid values 1, 2, 4 and 8. See Ethos(TM)-N documentation.")
257+
.set_default("1");
258+
TVM_ATTR_FIELD(ple_ratio)
259+
.describe("Valid values 2 and 4. See Ethos(TM)-N documentation.")
260+
.set_default("2");
253261
TVM_ATTR_FIELD(strategy0).set_default(true);
254262
TVM_ATTR_FIELD(strategy1).set_default(true);
255263
TVM_ATTR_FIELD(strategy3).set_default(true);
@@ -339,6 +347,10 @@ class EthosnCompiler {
339347
static std::pair<std::vector<uint32_t>, std::vector<uint32_t>> GetInputOutputOrder(
340348
NetworkWithIDs network, const std::unique_ptr<sl::CompiledNetwork>& compiled_network);
341349

350+
/*!
351+
* \brief Query interface used to determine if the Ethos-N hardware supports an operation
352+
* with the supplied parameters.
353+
*/
342354
static std::unique_ptr<sl::SupportQueries> m_Queries;
343355
};
344356

tests/python/contrib/test_ethosn/infrastructure.py

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -254,7 +254,9 @@ def inference_result(outputs):
254254

255255
def test_error(mod, params, err_msg):
256256
caught = None
257-
with tvm.transform.PassContext(opt_level=3):
257+
with tvm.transform.PassContext(
258+
opt_level=3, config={"relay.ext.ethos-n.options": {"variant": get_ethosn_variant()}}
259+
):
258260
with tvm.target.Target("llvm"):
259261
try:
260262
mod = relay.transform.InferType()(mod)
@@ -324,7 +326,4 @@ def get_ethosn_api_version():
324326

325327

326328
def get_ethosn_variant():
327-
ethosn_variant_config = os.getenv("ETHOSN_VARIANT_CONFIG")
328-
if ethosn_variant_config is not None:
329-
return "Ethos-N78_1TOPS_2PLE_RATIO"
330-
return "Ethos-N77"
329+
return os.getenv("ETHOSN_VARIANT_CONFIG", default="Ethos-N77")
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
# Licensed to the Apache Software Foundation (ASF) under one
2+
# or more contributor license agreements. See the NOTICE file
3+
# distributed with this work for additional information
4+
# regarding copyright ownership. The ASF licenses this file
5+
# to you under the Apache License, Version 2.0 (the
6+
# "License"); you may not use this file except in compliance
7+
# with the License. You may obtain a copy of the License at
8+
#
9+
# http://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# Unless required by applicable law or agreed to in writing,
12+
# software distributed under the License is distributed on an
13+
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
# KIND, either express or implied. See the License for the
15+
# specific language governing permissions and limitations
16+
# under the License.
17+
"""Ethos(TM)-N partition parameter tests"""
18+
19+
import pytest
20+
import tvm
21+
from tvm import relay
22+
import numpy as np
23+
24+
from tvm.relay.op.contrib.ethosn import partition_for_ethosn77
25+
from tvm.relay.op.contrib.ethosn import partition_for_ethosn78
26+
from tvm.testing import requires_ethosn
27+
28+
29+
@requires_ethosn
30+
def test_ethosn78_partition_no_error():
31+
a = relay.var("a", shape=[2, 7, 8, 8], dtype="uint8")
32+
w = relay.const(np.random.uniform(-10, 10, (8, 7, 3, 3)).astype("uint8"))
33+
res = relay.nn.conv2d(a, w, kernel_size=(3, 3), padding=(1, 1), channels=8, out_dtype="uint8")
34+
b = relay.var("b", shape=[8], dtype="uint8")
35+
res = relay.nn.bias_add(res, b, axis=1)
36+
37+
mod = tvm.IRModule.from_expr(res)
38+
opts = {"variant": "Ethos-N78"}
39+
partition_for_ethosn78(mod, **opts)
40+
41+
42+
@requires_ethosn
43+
def test_ethosn78_partition_undefined_variant():
44+
with pytest.raises(
45+
ValueError, match=r".*When targeting Ethos\(TM\)-N78, -variant=Ethos-N78 should be set.*"
46+
):
47+
a = relay.var("a", shape=[2, 7, 8, 8], dtype="uint8")
48+
w = relay.const(np.random.uniform(-10, 10, (8, 7, 3, 3)).astype("uint8"))
49+
res = relay.nn.conv2d(
50+
a, w, kernel_size=(3, 3), padding=(1, 1), channels=8, out_dtype="uint8"
51+
)
52+
b = relay.var("b", shape=[8], dtype="uint8")
53+
res = relay.nn.bias_add(res, b, axis=1)
54+
55+
mod = tvm.IRModule.from_expr(res)
56+
partition_for_ethosn78(mod)
57+
58+
59+
@requires_ethosn
60+
def test_ethosn78_partition_invalid_variant():
61+
with pytest.raises(
62+
ValueError, match=r".*When targeting Ethos\(TM\)-N78, -variant=Ethos-N78 should be set.*"
63+
):
64+
a = relay.var("a", shape=[2, 7, 8, 8], dtype="uint8")
65+
w = relay.const(np.random.uniform(-10, 10, (8, 7, 3, 3)).astype("uint8"))
66+
res = relay.nn.conv2d(
67+
a, w, kernel_size=(3, 3), padding=(1, 1), channels=8, out_dtype="uint8"
68+
)
69+
b = relay.var("b", shape=[8], dtype="uint8")
70+
res = relay.nn.bias_add(res, b, axis=1)
71+
72+
mod = tvm.IRModule.from_expr(res)
73+
opts = {"variant": "Ethos-N"}
74+
partition_for_ethosn78(mod, **opts)
75+
76+
77+
@requires_ethosn
78+
def test_ethosn78_partition_error():
79+
with pytest.raises(
80+
ValueError, match=r".*When targeting Ethos\(TM\)-N78, -variant=Ethos-N78 should be set.*"
81+
):
82+
a = relay.var("a", shape=[2, 7, 8, 8], dtype="uint8")
83+
w = relay.const(np.random.uniform(-10, 10, (8, 7, 3, 3)).astype("uint8"))
84+
res = relay.nn.conv2d(
85+
a, w, kernel_size=(3, 3), padding=(1, 1), channels=8, out_dtype="uint8"
86+
)
87+
b = relay.var("b", shape=[8], dtype="uint8")
88+
res = relay.nn.bias_add(res, b, axis=1)
89+
90+
mod = tvm.IRModule.from_expr(res)
91+
opts = {"variant": "Ethos-N77"}
92+
partition_for_ethosn78(mod, **opts)
93+
94+
95+
@requires_ethosn
96+
def test_ethosn77_partition_no_error():
97+
a = relay.var("a", shape=[2, 7, 8, 8], dtype="uint8")
98+
w = relay.const(np.random.uniform(-10, 10, (8, 7, 3, 3)).astype("uint8"))
99+
res = relay.nn.conv2d(a, w, kernel_size=(3, 3), padding=(1, 1), channels=8, out_dtype="uint8")
100+
b = relay.var("b", shape=[8], dtype="uint8")
101+
res = relay.nn.bias_add(res, b, axis=1)
102+
103+
mod = tvm.IRModule.from_expr(res)
104+
partition_for_ethosn77(mod)
105+
106+
107+
@requires_ethosn
108+
def test_ethosn77_partition_error():
109+
with pytest.raises(
110+
ValueError,
111+
match=r".*Setting tops, ple_ratio or sram_size has no effect when targeting Ethos\(TM\)-N77.*",
112+
):
113+
a = relay.var("a", shape=[2, 7, 8, 8], dtype="uint8")
114+
w = relay.const(np.random.uniform(-10, 10, (8, 7, 3, 3)).astype("uint8"))
115+
res = relay.nn.conv2d(
116+
a, w, kernel_size=(3, 3), padding=(1, 1), channels=8, out_dtype="uint8"
117+
)
118+
b = relay.var("b", shape=[8], dtype="uint8")
119+
res = relay.nn.bias_add(res, b, axis=1)
120+
121+
mod = tvm.IRModule.from_expr(res)
122+
opts = {"tops": 4}
123+
partition_for_ethosn77(mod, **opts)

tests/python/driver/tvmc/test_compiler.py

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
import pytest
2525

2626
import tvm
27-
import tvm.testing
27+
from tvm.testing.utils import ethosn_available
2828

2929
from tvm.contrib.target.vitis_ai import vitis_ai_available
3030

@@ -370,8 +370,11 @@ def test_compile_opencl(tflite_mobilenet_v1_0_25_128):
370370
assert os.path.exists(dumps_path)
371371

372372

373-
@tvm.testing.requires_ethosn
374-
def test_compile_tflite_module_with_external_codegen(tflite_mobilenet_v1_1_quant):
373+
@pytest.mark.skipif(
374+
not ethosn_available(),
375+
reason="--target=Ethos(TM)-N77 is not available. TVM built with 'USE_ETHOSN OFF'",
376+
)
377+
def test_compile_tflite_module_with_external_codegen_ethos_n77(tflite_mobilenet_v1_1_quant):
375378
pytest.importorskip("tflite")
376379
tvmc_model = tvmc.load(tflite_mobilenet_v1_1_quant)
377380
tvmc_package = tvmc.compile(tvmc_model, target="ethos-n77, llvm", dump_code="relay")
@@ -416,6 +419,26 @@ def test_compile_tflite_module_with_external_codegen_cmsisnn(
416419
assert len(c_source_files) == 3
417420

418421

422+
@pytest.mark.skipif(
423+
not ethosn_available(),
424+
reason="--target=Ethos(TM)-N78 is not available. TVM built with 'USE_ETHOSN OFF'",
425+
)
426+
def test_compile_tflite_module_with_external_codegen_ethos_n78(tflite_mobilenet_v1_1_quant):
427+
pytest.importorskip("tflite")
428+
tvmc_model = tvmc.load(tflite_mobilenet_v1_1_quant)
429+
tvmc_package = tvmc.compile(
430+
tvmc_model, target="ethos-n78 -variant=ethos-n78, llvm", dump_code="relay"
431+
)
432+
dumps_path = tvmc_package.package_path + ".relay"
433+
434+
# check for output types
435+
assert type(tvmc_package) is TVMCPackage
436+
assert type(tvmc_package.graph) is str
437+
assert type(tvmc_package.lib_path) is str
438+
assert type(tvmc_package.params) is bytearray
439+
assert os.path.exists(dumps_path)
440+
441+
419442
@pytest.mark.skipif(
420443
not vitis_ai_available(),
421444
reason="--target=vitis-ai is not available. TVM built with 'USE_VITIS_AI OFF'",

tests/python/driver/tvmc/test_composite_target.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ def test_get_codegen_names():
3434
names = tvmc.composite_target.get_codegen_names()
3535

3636
assert "ethos-n77" in names
37+
assert "ethos-n78" in names
3738
assert "vitis-ai" in names
3839
assert len(names) > 0
3940

0 commit comments

Comments
 (0)