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
5 changes: 4 additions & 1 deletion python/tvm/micro/testing/evaluation.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import tempfile

import tvm
from tvm.relay.op.contrib import cmsisnn


def tune_model(
Expand Down Expand Up @@ -99,6 +100,8 @@ def create_aot_session(
if use_cmsis_nn:
config["relay.ext.cmsisnn.options"] = {"mcpu": target.mcpu}
stack.enter_context(tvm.transform.PassContext(opt_level=3, config=config))
if use_cmsis_nn:
mod = cmsisnn.partition_for_cmsisnn(mod, params, mcpu=target.mcpu)
if tune_logs is not None:
stack.enter_context(tvm.autotvm.apply_history_best(tune_logs))

Expand Down Expand Up @@ -153,4 +156,4 @@ def evaluate_model_accuracy(session, aot_executor, input_data, true_labels, runs
num_correct = sum(u == v for u, v in zip(true_labels, predicted_labels))
average_time = sum(aot_runtimes) / len(aot_runtimes)
accuracy = num_correct / len(predicted_labels)
return average_time, accuracy
return average_time, accuracy, predicted_labels
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are we changing this to return predicted_labels? There are a few cases where we'll want to override evaluate_model_accuracy (say, with the MLPerf Tiny model for anomaly detection) but there won't be a 1:1 correspondence of samples to predicted_labels (because anomaly detection uses an area of the curve metric). I'd prefer to keep it as is.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I made this change since we were using the predicted labels outside of this function for validity check in some tests. If you think it's out of the scope of this function, I can replicated the whole function in the other repo. thoughts?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, I'd forgotten we need the labels for some hardware in the loop tests. That seems fine - for anomaly detection and other AOC metrics using the same "template", we could use confidence values (or even just None) in place of predicted_labels. This LGTM now.

111 changes: 111 additions & 0 deletions python/tvm/micro/testing/pytest_plugin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.

# pylint: disable=invalid-name,redefined-outer-name
""" microTVM testing fixtures used to deduce testing argument
values from testing parameters """

import pathlib
import os
import datetime
import pytest

from tvm.contrib.utils import tempdir

from .utils import get_supported_boards


def pytest_addoption(parser):
"""Adds more pytest arguments"""
parser.addoption(
"--board",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Love this change! It has been a long time in the workds.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should also document explicitly the difference between platforms and boards in our usage - it might not be obvious to others.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

added more details

required=True,
choices=list(get_supported_boards("zephyr").keys())
+ list(get_supported_boards("arduino").keys()),
help=(
"microTVM boards for tests. Board refers to instances"
"of microcontrollers/emulators defined in a platform."
),
)
parser.addoption(
"--test-build-only",
action="store_true",
help="Only run tests that don't require physical hardware.",
)
parser.addoption(
"--microtvm-debug",
action="store_true",
default=False,
help=(
"If set true, it will keep the project directory for debugging."
"Also, it will enable debug level logging in project generation."
),
)


@pytest.fixture(scope="session")
def board(request):
return request.config.getoption("--board")


@pytest.fixture(scope="session")
def microtvm_debug(request):
return request.config.getoption("--microtvm-debug")


def pytest_collection_modifyitems(config, items):
if config.getoption("--test-build-only"):
skip_hardware_tests = pytest.mark.skip(reason="--test-build-only was passed")
for item in items:
if "requires_hardware" in item.keywords:
item.add_marker(skip_hardware_tests)


@pytest.fixture
def workspace_dir(request, board, microtvm_debug):
"""Creates workspace directory for each test."""
parent_dir = pathlib.Path(os.path.dirname(request.module.__file__))
board_workspace = (
parent_dir / f"workspace_{board}" / datetime.datetime.now().strftime("%Y-%m-%dT%H-%M-%S")
)
board_workspace_base = str(board_workspace)
number = 1
while board_workspace.exists():
board_workspace = pathlib.Path(board_workspace_base + f"-{number}")
number += 1

if not os.path.exists(board_workspace.parent):
os.makedirs(board_workspace.parent)

keep_for_debug = microtvm_debug if microtvm_debug else None
test_temp_dir = tempdir(custom_path=board_workspace, keep_for_debug=keep_for_debug)
return test_temp_dir


@pytest.fixture(autouse=True)
def skip_by_board(request, board):
"""Skip test if board is in the list."""
if request.node.get_closest_marker("skip_boards"):
if board in request.node.get_closest_marker("skip_boards").args[0]:
pytest.skip("skipped on this board: {}".format(board))


def pytest_configure(config):
config.addinivalue_line(
"markers",
"skip_boards(board): skip test for the given board",
)
2 changes: 2 additions & 0 deletions tests/micro/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# test workspaces
*/workspace_*/
45 changes: 4 additions & 41 deletions tests/micro/arduino/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,35 +15,19 @@
# specific language governing permissions and limitations
# under the License.

import pytest
pytest_plugins = [
"tvm.micro.testing.pytest_plugin",
]

from test_utils import ARDUINO_BOARDS
import pytest


def pytest_addoption(parser):
parser.addoption(
"--arduino-board",
nargs="+",
required=True,
choices=ARDUINO_BOARDS.keys(),
help="Arduino board for tests.",
)
parser.addoption(
"--arduino-cli-cmd",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it worth abstracting this parameter to "build tool path"? I could be convinced either way.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this way is more clear, specially when used in tvmc

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yea you’re right, build tool is just too abstract.

default="arduino-cli",
help="Path to `arduino-cli` command for flashing device.",
)
parser.addoption(
"--test-build-only",
action="store_true",
help="Only run tests that don't require physical hardware.",
)
parser.addoption(
"--tvm-debug",
action="store_true",
default=False,
help="If given, enable a debug session while the test is running. Before running the test, in a separate shell, you should run: <python -m tvm.exec.microtvm_debug_shell>",
)


def pytest_configure(config):
Expand All @@ -52,27 +36,6 @@ def pytest_configure(config):
)


def pytest_collection_modifyitems(config, items):
if config.getoption("--test-build-only"):
skip_hardware_tests = pytest.mark.skip(reason="--test-build-only was passed")
for item in items:
if "requires_hardware" in item.keywords:
item.add_marker(skip_hardware_tests)


# We might do project generation differently for different boards in the future
# (to take advantage of multiple cores / external memory / etc.), so all tests
# are parameterized by board
def pytest_generate_tests(metafunc):
board = metafunc.config.getoption("arduino_board")
metafunc.parametrize("board", board, scope="session")


@pytest.fixture(scope="session")
def arduino_cli_cmd(request):
return request.config.getoption("--arduino-cli-cmd")


@pytest.fixture(scope="session")
def tvm_debug(request):
return request.config.getoption("--tvm-debug")
9 changes: 2 additions & 7 deletions tests/micro/arduino/test_arduino_error_detection.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,10 @@
import test_utils
import tvm.testing

# A new project and workspace dir is created for EVERY test
@pytest.fixture
def workspace_dir(request, board):
return test_utils.make_workspace_dir("arduino_error_detection", board)


@pytest.fixture
def project(board, arduino_cli_cmd, tvm_debug, workspace_dir):
return test_utils.make_kws_project(board, arduino_cli_cmd, tvm_debug, workspace_dir)
def project(board, arduino_cli_cmd, microtvm_debug, workspace_dir):
return test_utils.make_kws_project(board, arduino_cli_cmd, microtvm_debug, workspace_dir)


def test_blank_project_compiles(workspace_dir, project):
Expand Down
29 changes: 12 additions & 17 deletions tests/micro/arduino/test_arduino_rpc_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,6 @@

import test_utils

# # A new project and workspace dir is created for EVERY test
@pytest.fixture
def workspace_dir(board):
return test_utils.make_workspace_dir("arduino_rpc_server", board)


def _make_session(model, arduino_board, arduino_cli_cmd, workspace_dir, mod, build_config):
project = tvm.micro.generate_project(
Expand Down Expand Up @@ -84,11 +79,11 @@ def _make_add_sess(model, arduino_board, arduino_cli_cmd, workspace_dir, build_c
# The same test code can be executed on both the QEMU simulation and on real hardware.
@tvm.testing.requires_micro
@pytest.mark.requires_hardware
def test_compile_runtime(board, arduino_cli_cmd, tvm_debug, workspace_dir):
def test_compile_runtime(board, arduino_cli_cmd, microtvm_debug, workspace_dir):
"""Test compiling the on-device runtime."""

model = test_utils.ARDUINO_BOARDS[board]
build_config = {"debug": tvm_debug}
build_config = {"debug": microtvm_debug}

# NOTE: run test in a nested function so cPython will delete arrays before closing the session.
def test_basic_add(sess):
Expand All @@ -109,11 +104,11 @@ def test_basic_add(sess):

@tvm.testing.requires_micro
@pytest.mark.requires_hardware
def test_platform_timer(board, arduino_cli_cmd, tvm_debug, workspace_dir):
def test_platform_timer(board, arduino_cli_cmd, microtvm_debug, workspace_dir):
"""Test compiling the on-device runtime."""

model = test_utils.ARDUINO_BOARDS[board]
build_config = {"debug": tvm_debug}
build_config = {"debug": microtvm_debug}

# NOTE: run test in a nested function so cPython will delete arrays before closing the session.
def test_basic_add(sess):
Expand All @@ -139,10 +134,10 @@ def test_basic_add(sess):

@tvm.testing.requires_micro
@pytest.mark.requires_hardware
def test_relay(board, arduino_cli_cmd, tvm_debug, workspace_dir):
def test_relay(board, arduino_cli_cmd, microtvm_debug, workspace_dir):
"""Testing a simple relay graph"""
model = test_utils.ARDUINO_BOARDS[board]
build_config = {"debug": tvm_debug}
build_config = {"debug": microtvm_debug}

shape = (10,)
dtype = "int8"
Expand Down Expand Up @@ -172,10 +167,10 @@ def test_relay(board, arduino_cli_cmd, tvm_debug, workspace_dir):

@tvm.testing.requires_micro
@pytest.mark.requires_hardware
def test_onnx(board, arduino_cli_cmd, tvm_debug, workspace_dir):
def test_onnx(board, arduino_cli_cmd, microtvm_debug, workspace_dir):
"""Testing a simple ONNX model."""
model = test_utils.ARDUINO_BOARDS[board]
build_config = {"debug": tvm_debug}
build_config = {"debug": microtvm_debug}

# Load test images.
this_dir = pathlib.Path(__file__).parent
Expand Down Expand Up @@ -263,10 +258,10 @@ def check_result(

@tvm.testing.requires_micro
@pytest.mark.requires_hardware
def test_byoc_microtvm(board, arduino_cli_cmd, tvm_debug, workspace_dir):
def test_byoc_microtvm(board, arduino_cli_cmd, microtvm_debug, workspace_dir):
"""This is a simple test case to check BYOC capabilities of microTVM"""
model = test_utils.ARDUINO_BOARDS[board]
build_config = {"debug": tvm_debug}
build_config = {"debug": microtvm_debug}

x = relay.var("x", shape=(10, 10))
w0 = relay.var("w0", shape=(10, 10))
Expand Down Expand Up @@ -347,10 +342,10 @@ def _make_add_sess_with_shape(
)
@tvm.testing.requires_micro
@pytest.mark.requires_hardware
def test_rpc_large_array(board, arduino_cli_cmd, tvm_debug, workspace_dir, shape):
def test_rpc_large_array(board, arduino_cli_cmd, microtvm_debug, workspace_dir, shape):
"""Test large RPC array transfer."""
model = test_utils.ARDUINO_BOARDS[board]
build_config = {"debug": tvm_debug}
build_config = {"debug": microtvm_debug}

# NOTE: run test in a nested function so cPython will delete arrays before closing the session.
def test_tensors(sess):
Expand Down
13 changes: 7 additions & 6 deletions tests/micro/arduino/test_arduino_workflow.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,23 +36,24 @@
6. Use serial connection to ensure model behaves correctly
"""


# Since these tests are sequential, we'll use the same project/workspace
# directory for all tests in this file
@pytest.fixture(scope="module")
def workspace_dir(request, board):
def workflow_workspace_dir(request, board):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Like this change!

return test_utils.make_workspace_dir("arduino_workflow", board)


@pytest.fixture(scope="module")
def project_dir(workspace_dir):
return workspace_dir / "project"
def project_dir(workflow_workspace_dir):
return workflow_workspace_dir / "project"


# We MUST pass workspace_dir, not project_dir, or the workspace will be dereferenced too soon
@pytest.fixture(scope="module")
def project(board, arduino_cli_cmd, tvm_debug, workspace_dir):
return test_utils.make_kws_project(board, arduino_cli_cmd, tvm_debug, workspace_dir)
def project(board, arduino_cli_cmd, microtvm_debug, workflow_workspace_dir):
return test_utils.make_kws_project(
board, arduino_cli_cmd, microtvm_debug, workflow_workspace_dir
)


def _get_directory_elements(directory):
Expand Down
4 changes: 2 additions & 2 deletions tests/micro/arduino/test_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,10 +61,10 @@ def make_workspace_dir(test_name, board):
return t


def make_kws_project(board, arduino_cli_cmd, tvm_debug, workspace_dir):
def make_kws_project(board, arduino_cli_cmd, microtvm_debug, workspace_dir):
this_dir = pathlib.Path(__file__).parent
model = ARDUINO_BOARDS[board]
build_config = {"debug": tvm_debug}
build_config = {"debug": microtvm_debug}

mod, params = fetch_model_from_url(
url="https://github.com/tensorflow/tflite-micro/raw/main/tensorflow/lite/micro/examples/micro_speech/micro_speech.tflite",
Expand Down
Loading