diff --git a/.ci/scripts/setup-samsung-linux-deps.sh b/.ci/scripts/setup-samsung-linux-deps.sh index c1f2912713b..2f0a6fbf109 100644 --- a/.ci/scripts/setup-samsung-linux-deps.sh +++ b/.ci/scripts/setup-samsung-linux-deps.sh @@ -8,61 +8,199 @@ set -ex +API_KEY=$SAMSUNG_AI_LITECORE_KEY +if [[ -z "${API_KEY}" ]]; then + echo "ERROR: It didn't set up SAMSUNG_AI_LITECORE_KEY." >&2 + exit 1 +fi + +OS_NAME="Ubuntu 22.04" +LITECORE_BASE="https://soc-developer.semiconductor.samsung.com/api/v1/resource/ai-litecore/download" +DEVICEFARM_BASE="https://soc-developer.semiconductor.samsung.com/api/v1/resource/remotelab/download" + +parse_url() { + local json="$1" + if command -v jq >/dev/null 2>&1; then + jq -r '.data // empty' <<<"$json" + else + sed -n 's/.*"data":[[:space:]]*"\([^"]*\)".*/\1/p' <<<"$json" + fi +} -download_ai_lite_core() { - API_BASE="https://soc-developer.semiconductor.samsung.com/api/v1/resource/ai-litecore/download" - API_KEY=$SAMSUNG_AI_LITECORE_KEY - - VERSION="0.7" - OS_NAME="Ubuntu 22.04" - OUT_FILE="/tmp/exynos-ai-litecore-v${VERSION}.tar.gz" - TARGET_PATH="/tmp/exynos_ai_lite_core" - - mkdir -p ${TARGET_PATH} - # Presigned issue URL - JSON_RESP=$(curl -sS -G \ - --location --fail --retry 3 \ +download_and_extract() { + local base_url="$1" + local version="$2" + local out_dir="$3" + local out_file="$4" + + local resp + resp=$(curl -fsSL -G \ -H "apikey: ${API_KEY}" \ - --data-urlencode "version=${VERSION}" \ + --data-urlencode "version=${version}" \ --data-urlencode "os=${OS_NAME}" \ - "${API_BASE}") + "${base_url}") + + local download_url + download_url=$(parse_url "$resp") + if [[ -z "${download_url}" ]]; then + echo "ERROR: It failed to download from ${base_url} ." + echo "Response: $resp" >&2 + exit 1 + fi + + curl -fsSL -L --retry 3 -o "${out_file}" "${download_url}" + echo "Download completed: ${out_file}" - DOWNLOAD_URL=$(echo "$JSON_RESP" | sed -n 's/.*"data":[[:space:]]*"\([^"]*\)".*/\1/p') + mkdir -p "${out_dir}" + case "${out_file##*.}" in + tar|tgz|gz) + echo "Extracting TAR.GZ..." + tar -C "${out_dir}" --strip-components=1 -xzvf "${out_file}" + ;; - if [[ -z "$DOWNLOAD_URL" ]]; then - echo "Failed to extract download URL" - echo "$JSON_RESP" + zip) + echo "Extracting ZIP..." + unzip -q -d "${out_dir}" "${out_file}" + ;; + + *) exit 1 + ;; + esac + echo "Extracted to: ${out_dir}" +} + +download_ai_lite_core() { + local litecore_version="${1:-1.0}" + local litecore_out="/tmp/exynos-ai-litecore-v${litecore_version}.tar.gz" + local litecore_dir="/tmp/exynos_ai_lite_core" + + download_and_extract \ + "${LITECORE_BASE}" \ + "${litecore_version}" \ + "${litecore_dir}" \ + "${litecore_out}" + + export EXYNOS_AI_LITECORE_ROOT="${litecore_dir}" + export LD_LIBRARY_PATH="${LD_LIBRARY_PATH:-}:${EXYNOS_AI_LITECORE_ROOT}/lib/x86_64-linux" +} + +install_devicefarm_cli() { + local cli_version="${1:-beta-1.0.8}" + local cli_out="/tmp/devicefarm-cli-v${cli_version}.zip" + local cli_dir="/tmp/devicefarm_cli" + + download_and_extract \ + "${DEVICEFARM_BASE}" \ + "${cli_version}" \ + "${cli_dir}" \ + "${cli_out}" + + export PATH="${PATH%:}:${cli_dir}" + chmod +x "${cli_dir}/devicefarm-cli" +} + +reserve_if_needed() { + if ! command -v devicefarm-cli >/dev/null 2>&1; then + echo "[WARN] devicefarm-cli is not installed." >&2 + return 1 fi - # Download LiteCore - curl -sS -L --fail --retry 3 \ - --output "$OUT_FILE" \ - "$DOWNLOAD_URL" + local raw_info info_lines + raw_info="$(devicefarm-cli -I)" + + info_lines="$(printf '%s\n' "$raw_info" | grep -v '^\\[INFO\\]')" - echo "Download done: $OUT_FILE" + local found_count + found_count=$(printf '%s\n' "$info_lines" \ + | grep -Eo 'Found available reservations *: *[0-9]+' \ + | grep -Eo '[0-9]+') + [[ -z "$found_count" ]] && found_count=0 - tar -C "${TARGET_PATH}" --strip-components=1 -xzvf "${OUT_FILE}" + echo "[INFO] Current Reserved Count: $found_count" + + local THRESHOLD_SECONDS=12600 + local any_below_threshold=0 + + if (( found_count > 0 )); then + local table_body + table_body=$(printf '%s\n' "$info_lines" | sed -n '2,$p') + + while IFS= read -r line; do + if [[ "$line" =~ ^[0-9]+[[:space:]]+([0-9]{1,2}:[0-9]{2}:[0-9]{2}) ]]; then + local time_str="${BASH_REMATCH[1]}" + IFS=: read -r hh mm ss <<<"$time_str" + (( seconds = 10#$hh * 3600 + 10#$mm * 60 + 10#$ss )) + if (( seconds <= THRESHOLD_SECONDS )); then + any_below_threshold=1 + break + fi + fi + done <<<"$table_body" + else + any_below_threshold=1 + fi - export EXYNOS_AI_LITECORE_ROOT=${TARGET_PATH} - export LD_LIBRARY_PATH=${LD_LIBRARY_PATH:-}:${EXYNOS_AI_LITECORE_ROOT}/lib/x86_64-linux + if (( any_below_threshold )); then + echo "[INFO] Reserving now." + devicefarm-cli -R + else + echo "[INFO] Don't need to be reserved." + fi + + local info_after reservation_id max_seconds=0 max_id + + info_after="$(devicefarm-cli -I)" + + local body_after + body_after=$(printf '%s\n' "$info_after" | grep -v '^\\[INFO\\]' | sed -n '2,$p') + + while IFS= read -r line; do + if [[ "$line" =~ ^[0-9]+[[:space:]]+([0-9]{1,2}:[0-9]{2}:[0-9]{2})[[:space:]].*([0-9a-f-]{36})$ ]]; then + local time_str="${BASH_REMATCH[1]}" + local id="${BASH_REMATCH[2]}" + IFS=: read -r hh mm ss <<<"$time_str" + (( seconds = 10#$hh * 3600 + 10#$mm * 60 + 10#$ss )) + if (( seconds > max_seconds )); then + max_seconds=$seconds + max_id=$id + fi + fi + done <<<"$body_after" + + reservation_id=$max_id + + if [[ -n "$reservation_id" ]]; then + devicefarm-cli -C "$reservation_id" + devicefarm-cli -E "ls /" + else + echo "[WARN] There is no available devices." + fi } install_enn_backend() { - NDK_INSTALLATION_DIR=/opt/ndk - rm -rf "${NDK_INSTALLATION_DIR}" && sudo mkdir -p "${NDK_INSTALLATION_DIR}" - ANDROID_NDK_VERSION=r28c + local ndk_dir="/opt/ndk" + local ndk_version="r28c" + + if [[ ! -d "${ndk_dir}" ]]; then + sudo mkdir -p "${ndk_dir}" + sudo chown "$(whoami)":"$(whoami)" "${ndk_dir}" + fi + + export ANDROID_NDK_ROOT="${ndk_dir}" + echo "NDK will be installed/used at: ${ANDROID_NDK_ROOT}" - # build Exynos backend - export ANDROID_NDK_ROOT=${ANDROID_NDK_ROOT:-/opt/ndk} bash backends/samsung/build.sh --build all - # set env variable - export EXECUTORCH_ROOT="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/../.." && pwd)" - export PYTHONPATH=${PYTHONPATH:-}:${EXECUTORCH_ROOT}/.. + + export EXECUTORCH_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" + export PYTHONPATH="${PYTHONPATH:-}:${EXECUTORCH_ROOT}/.." } -AI_LITE_CORE_VERSION=0.7.0 +litecore_ver="1.0" +devicefarm_ver="beta-1.0.8" -download_ai_lite_core ${AI_LITE_CORE_VERSION} +download_ai_lite_core ${litecore_ver} +install_devicefarm_cli "${devicefarm_ver}" install_enn_backend +reserve_if_needed diff --git a/.github/workflows/pull.yml b/.github/workflows/pull.yml index 6f83f7b45e6..6bdf08c3e30 100644 --- a/.github/workflows/pull.yml +++ b/.github/workflows/pull.yml @@ -915,9 +915,8 @@ jobs: PYTHON_EXECUTABLE=python bash examples/nxp/run_aot_example.sh cifar10 PYTHON_EXECUTABLE=python bash examples/nxp/run_aot_example.sh mobilenetv2 - - test-samsung-models-linux: - name: test-samsung-models-linux + test-samsung-quantmodels-linux: + name: test-samsung-quantmodels-linux # Skip this job if the pull request is from a fork (secrets are not available) if: github.event.pull_request.head.repo.full_name == github.repository || github.event_name != 'pull_request' uses: pytorch/test-infra/.github/workflows/linux_job_v2.yml@main @@ -931,7 +930,7 @@ jobs: docker-image: ci-image:executorch-ubuntu-22.04-clang12-android submodules: 'recursive' ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }} - timeout: 90 + timeout: 180 script: | set -ex @@ -946,21 +945,44 @@ jobs: export SAMSUNG_AI_LITECORE_KEY=$SECRET_SAMSUNG_AI_LITECORE_KEY source .ci/scripts/setup-samsung-linux-deps.sh - # Test models serially - models="mv2 ic3 resnet18 resnet50 mv3 ic4 dl3 edsr vit w2l" - for model in $models; do - python -m executorch.examples.samsung.aot_compiler --model_name=$model -c E9955 - done - # Test quant models model_scripts="deeplab_v3 edsr inception_v3 inception_v4 mobilenet_v2 mobilenet_v3 resnet18 resnet50 vit wav2letter" for m_script in $model_scripts; do python -m executorch.examples.samsung.scripts.${m_script} -c e9955 -p A8W8 done - # Test ops - python -m unittest discover -s backends/samsung/test/ops -p "test_*.py" + test-samsung-models-linux: + name: test-samsung-models-linux + # Skip this job if the pull request is from a fork (secrets are not available) + if: github.event.pull_request.head.repo.full_name == github.repository || github.event_name != 'pull_request' + uses: pytorch/test-infra/.github/workflows/linux_job_v2.yml@main + permissions: + id-token: write + contents: read + secrets: inherit + with: + secrets-env: SAMSUNG_AI_LITECORE_KEY + runner: linux.2xlarge + docker-image: ci-image:executorch-ubuntu-22.04-clang12-android + submodules: 'recursive' + ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }} + timeout: 360 + script: | + set -ex + + # The generic Linux job chooses to use base env, not the one setup by the image + CONDA_ENV=$(conda env list --json | jq -r ".envs | .[-1]") + conda activate "${CONDA_ENV}" + + # Setup python + PYTHON_EXECUTABLE=python bash .ci/scripts/setup-linux.sh --build-tool "cmake" + + # Setup Samsung SDK (AI Lite Core) and install enn backend + export SAMSUNG_AI_LITECORE_KEY=$SECRET_SAMSUNG_AI_LITECORE_KEY + source .ci/scripts/setup-samsung-linux-deps.sh + # Test models + python -m unittest discover -s backends/samsung/test/models -p "test_*.py" test-vulkan-models-linux: name: test-vulkan-models-linux diff --git a/backends/samsung/test/models/test_deeplab_v3.py b/backends/samsung/test/models/test_deeplab_v3.py new file mode 100644 index 00000000000..a2b3fcb93a0 --- /dev/null +++ b/backends/samsung/test/models/test_deeplab_v3.py @@ -0,0 +1,28 @@ +# Copyright (c) Samsung Electronics Co. LTD +# All rights reserved +# +# Licensed under the BSD License (the "License"); you may not use this file +# except in compliance with the License. See the license file in the root +# directory of this source tree for more details. +import unittest + +from executorch.backends.samsung.serialization.compile_options import ( + gen_samsung_backend_compile_spec, +) +from executorch.backends.samsung.test.tester import SamsungTester +from executorch.examples.models.deeplab_v3 import DeepLabV3ResNet50Model + + +class TestMilestoneDeepLabV3(unittest.TestCase): + def test_dl3_fp16(self): + model = DeepLabV3ResNet50Model().get_eager_model() + example_input = DeepLabV3ResNet50Model().get_example_inputs() + tester = SamsungTester( + model, example_input, [gen_samsung_backend_compile_spec("E9955")] + ) + ( + tester.export() + .to_edge_transform_and_lower() + .to_executorch() + .run_method_and_compare_outputs(inputs=example_input, atol=0.009) + ) diff --git a/backends/samsung/test/models/test_edsr.py b/backends/samsung/test/models/test_edsr.py new file mode 100644 index 00000000000..326296fc55a --- /dev/null +++ b/backends/samsung/test/models/test_edsr.py @@ -0,0 +1,30 @@ +# Copyright (c) Samsung Electronics Co. LTD +# All rights reserved +# +# Licensed under the BSD License (the "License"); you may not use this file +# except in compliance with the License. See the license file in the root +# directory of this source tree for more details. + + +import unittest + +from executorch.backends.samsung.serialization.compile_options import ( + gen_samsung_backend_compile_spec, +) +from executorch.backends.samsung.test.tester import SamsungTester +from executorch.examples.models.edsr import EdsrModel + + +class TestMilestoneEdsr(unittest.TestCase): + def test_edsr_fp16(self): + model = EdsrModel().get_eager_model() + example_input = EdsrModel().get_example_inputs() + tester = SamsungTester( + model, example_input, [gen_samsung_backend_compile_spec("E9955")] + ) + ( + tester.export() + .to_edge_transform_and_lower() + .to_executorch() + .run_method_and_compare_outputs(inputs=example_input, atol=0.02) + ) diff --git a/backends/samsung/test/models/test_inception_v3.py b/backends/samsung/test/models/test_inception_v3.py new file mode 100644 index 00000000000..f3b753b946c --- /dev/null +++ b/backends/samsung/test/models/test_inception_v3.py @@ -0,0 +1,30 @@ +# Copyright (c) Samsung Electronics Co. LTD +# All rights reserved +# +# Licensed under the BSD License (the "License"); you may not use this file +# except in compliance with the License. See the license file in the root +# directory of this source tree for more details. + + +import unittest + +from executorch.backends.samsung.serialization.compile_options import ( + gen_samsung_backend_compile_spec, +) +from executorch.backends.samsung.test.tester import SamsungTester +from executorch.examples.models.inception_v3 import InceptionV3Model + + +class TestInceptionV3(unittest.TestCase): + def test_inception_v3_fp16(self): + model = InceptionV3Model().get_eager_model() + example_input = InceptionV3Model().get_example_inputs() + tester = SamsungTester( + model, example_input, [gen_samsung_backend_compile_spec("E9955")] + ) + ( + tester.export() + .to_edge_transform_and_lower() + .to_executorch() + .run_method_and_compare_outputs(inputs=example_input, atol=0.02, rtol=0.02) + ) diff --git a/backends/samsung/test/models/test_inception_v4.py b/backends/samsung/test/models/test_inception_v4.py new file mode 100644 index 00000000000..53bd209d5d2 --- /dev/null +++ b/backends/samsung/test/models/test_inception_v4.py @@ -0,0 +1,30 @@ +# Copyright (c) Samsung Electronics Co. LTD +# All rights reserved +# +# Licensed under the BSD License (the "License"); you may not use this file +# except in compliance with the License. See the license file in the root +# directory of this source tree for more details. + + +import unittest + +from executorch.backends.samsung.serialization.compile_options import ( + gen_samsung_backend_compile_spec, +) +from executorch.backends.samsung.test.tester import SamsungTester +from executorch.examples.models.inception_v4 import InceptionV4Model + + +class TestMilestoneInceptionV4(unittest.TestCase): + def test_inception_v4_fp16(self): + model = InceptionV4Model().get_eager_model() + example_input = InceptionV4Model().get_example_inputs() + tester = SamsungTester( + model, example_input, [gen_samsung_backend_compile_spec("E9955")] + ) + ( + tester.export() + .to_edge_transform_and_lower() + .to_executorch() + .run_method_and_compare_outputs(inputs=example_input, atol=0.02, rtol=0.02) + ) diff --git a/backends/samsung/test/models/test_mobilenet_v2.py b/backends/samsung/test/models/test_mobilenet_v2.py new file mode 100644 index 00000000000..86805e5cbc2 --- /dev/null +++ b/backends/samsung/test/models/test_mobilenet_v2.py @@ -0,0 +1,28 @@ +# Copyright (c) Samsung Electronics Co. LTD +# All rights reserved +# +# Licensed under the BSD License (the "License"); you may not use this file +# except in compliance with the License. See the license file in the root +# directory of this source tree for more details. +import unittest + +from executorch.backends.samsung.serialization.compile_options import ( + gen_samsung_backend_compile_spec, +) +from executorch.backends.samsung.test.tester import SamsungTester +from executorch.examples.models.mobilenet_v2 import MV2Model + + +class TestMilestoneMobilenetV2(unittest.TestCase): + def test_mv2_fp16(self): + model = MV2Model().get_eager_model() + example_input = MV2Model().get_example_inputs() + tester = SamsungTester( + model, example_input, [gen_samsung_backend_compile_spec("E9955")] + ) + ( + tester.export() + .to_edge_transform_and_lower() + .to_executorch() + .run_method_and_compare_outputs(inputs=example_input, atol=0.02) + ) diff --git a/backends/samsung/test/models/test_mobilenet_v3.py b/backends/samsung/test/models/test_mobilenet_v3.py new file mode 100644 index 00000000000..669cca1db12 --- /dev/null +++ b/backends/samsung/test/models/test_mobilenet_v3.py @@ -0,0 +1,33 @@ +# Copyright (c) Samsung Electronics Co. LTD +# All rights reserved +# +# Licensed under the BSD License (the "License"); you may not use this file +# except in compliance with the License. See the license file in the root +# directory of this source tree for more details. + + +import unittest + +import torch + +from executorch.backends.samsung.serialization.compile_options import ( + gen_samsung_backend_compile_spec, +) +from executorch.backends.samsung.test.tester import SamsungTester +from executorch.examples.models.mobilenet_v3 import MV3Model + + +class TestMilestoneMobilenetV3(unittest.TestCase): + def test_mv3_fp16(self): + torch.manual_seed(8) + model = MV3Model().get_eager_model() + example_input = MV3Model().get_example_inputs() + tester = SamsungTester( + model, example_input, [gen_samsung_backend_compile_spec("E9955")] + ) + ( + tester.export() + .to_edge_transform_and_lower() + .to_executorch() + .run_method_and_compare_outputs(inputs=example_input, atol=0.07, rtol=0.07) + ) diff --git a/backends/samsung/test/models/test_resnet18.py b/backends/samsung/test/models/test_resnet18.py new file mode 100644 index 00000000000..429218649b8 --- /dev/null +++ b/backends/samsung/test/models/test_resnet18.py @@ -0,0 +1,30 @@ +# Copyright (c) Samsung Electronics Co. LTD +# All rights reserved +# +# Licensed under the BSD License (the "License"); you may not use this file +# except in compliance with the License. See the license file in the root +# directory of this source tree for more details. + + +import unittest + +from executorch.backends.samsung.serialization.compile_options import ( + gen_samsung_backend_compile_spec, +) +from executorch.backends.samsung.test.tester import SamsungTester +from executorch.examples.models.resnet import ResNet18Model + + +class TestMilestoneResNet18(unittest.TestCase): + def test_resnet18_fp16(self): + model = ResNet18Model().get_eager_model() + example_input = ResNet18Model().get_example_inputs() + tester = SamsungTester( + model, example_input, [gen_samsung_backend_compile_spec("E9955")] + ) + ( + tester.export() + .to_edge_transform_and_lower() + .to_executorch() + .run_method_and_compare_outputs(inputs=example_input, atol=0.02, rtol=0.02) + ) diff --git a/backends/samsung/test/models/test_resnet50.py b/backends/samsung/test/models/test_resnet50.py new file mode 100644 index 00000000000..0c6b32526b1 --- /dev/null +++ b/backends/samsung/test/models/test_resnet50.py @@ -0,0 +1,30 @@ +# Copyright (c) Samsung Electronics Co. LTD +# All rights reserved +# +# Licensed under the BSD License (the "License"); you may not use this file +# except in compliance with the License. See the license file in the root +# directory of this source tree for more details. + + +import unittest + +from executorch.backends.samsung.serialization.compile_options import ( + gen_samsung_backend_compile_spec, +) +from executorch.backends.samsung.test.tester import SamsungTester +from executorch.examples.models.resnet import ResNet50Model + + +class TestMilestoneResNet50(unittest.TestCase): + def test_resnet50_fp16(self): + model = ResNet50Model().get_eager_model() + example_input = ResNet50Model().get_example_inputs() + tester = SamsungTester( + model, example_input, [gen_samsung_backend_compile_spec("E9955")] + ) + ( + tester.export() + .to_edge_transform_and_lower() + .to_executorch() + .run_method_and_compare_outputs(inputs=example_input, atol=0.02, rtol=0.02) + ) diff --git a/backends/samsung/test/models/test_torchvision_vit.py b/backends/samsung/test/models/test_torchvision_vit.py new file mode 100644 index 00000000000..7cdb4cabada --- /dev/null +++ b/backends/samsung/test/models/test_torchvision_vit.py @@ -0,0 +1,32 @@ +# Copyright (c) Samsung Electronics Co. LTD +# All rights reserved +# +# Licensed under the BSD License (the "License"); you may not use this file +# except in compliance with the License. See the license file in the root +# directory of this source tree for more details. +import unittest + +import torch +from executorch.backends.samsung.serialization.compile_options import ( + gen_samsung_backend_compile_spec, +) +from executorch.backends.samsung.test.tester import SamsungTester +from executorch.examples.models.torchvision_vit import TorchVisionViTModel + + +class TestMilestoneTorchVisionViT(unittest.TestCase): + def test_torchvision_vit_fp16(self): + torch.manual_seed(8) + model = TorchVisionViTModel().get_eager_model() + example_input = TorchVisionViTModel().get_example_inputs() + tester = SamsungTester( + model, example_input, [gen_samsung_backend_compile_spec("E9955")] + ) + ( + tester.export() + .to_edge_transform_and_lower() + .to_executorch() + .run_method_and_compare_outputs( + inputs=example_input, atol=0.005, rtol=0.005 + ) + ) diff --git a/backends/samsung/test/models/test_wav2letter.py b/backends/samsung/test/models/test_wav2letter.py new file mode 100644 index 00000000000..4d016763b2b --- /dev/null +++ b/backends/samsung/test/models/test_wav2letter.py @@ -0,0 +1,28 @@ +# Copyright (c) Samsung Electronics Co. LTD +# All rights reserved +# +# Licensed under the BSD License (the "License"); you may not use this file +# except in compliance with the License. See the license file in the root +# directory of this source tree for more details. +import unittest + +from executorch.backends.samsung.serialization.compile_options import ( + gen_samsung_backend_compile_spec, +) +from executorch.backends.samsung.test.tester import SamsungTester +from executorch.examples.models.wav2letter import Wav2LetterModel + + +class TestMilestoneWav2Letter(unittest.TestCase): + def test_w2l_fp16(self): + model = Wav2LetterModel().get_eager_model() + example_input = Wav2LetterModel().get_example_inputs() + tester = SamsungTester( + model, example_input, [gen_samsung_backend_compile_spec("E9955")] + ) + ( + tester.export() + .to_edge_transform_and_lower() + .to_executorch() + .run_method_and_compare_outputs(inputs=example_input, atol=0.009) + ) diff --git a/backends/samsung/test/ops/test_add.py b/backends/samsung/test/ops/test_add.py index 8b1b4b4a770..58e49f7bb10 100644 --- a/backends/samsung/test/ops/test_add.py +++ b/backends/samsung/test/ops/test_add.py @@ -47,6 +47,7 @@ def _test(self, module: torch.nn.Module, inputs): .check_not(["executorch_exir_dialects_edge__ops_aten_add_Tensor"]) .check_count({"torch.ops.higher_order.executorch_call_delegate": 1}) .to_executorch() + .run_method_and_compare_outputs(inputs=inputs) ) def test_fp32_simple_add(self): diff --git a/backends/samsung/test/ops/test_avg_pool2d.py b/backends/samsung/test/ops/test_avg_pool2d.py index 2614516dd0d..e00f49a47fd 100644 --- a/backends/samsung/test/ops/test_avg_pool2d.py +++ b/backends/samsung/test/ops/test_avg_pool2d.py @@ -32,19 +32,15 @@ def __init__( ceil_mode=False, ).to(torch.float) - def get_example_inputs(self) -> tuple[torch.Tensor]: - input_1 = torch.randn(1, 16, 24, 24) - return (input_1,) - def forward(self, x: torch.Tensor) -> torch.Tensor: return self.avg_pool(x) class TestAvgPool2d(unittest.TestCase): - def _test(self, module: torch.nn.Module): + def _test(self, module: torch.nn.Module, inputs): tester = SamsungTester( module, - module.get_example_inputs(), + inputs, [gen_samsung_backend_compile_spec("E9955")], ) ( @@ -54,13 +50,17 @@ def _test(self, module: torch.nn.Module): .check_not(["executorch_exir_dialects_edge__ops_aten_avg_pool2d_default"]) .check_count({"torch.ops.higher_order.executorch_call_delegate": 1}) .to_executorch() + .run_method_and_compare_outputs(inputs=inputs) ) def test_fp32_avg_pool2d(self): - self._test(AvgPool2d()) + inputs = (torch.randn(1, 16, 24, 24),) + self._test(AvgPool2d(), inputs) def test_fp32_avg_pool2d_with_stride(self): - self._test(AvgPool2d(stride=2)) + inputs = (torch.randn(1, 16, 24, 24),) + self._test(AvgPool2d(stride=2), inputs) def test_fp32_avg_pool2d_with_kernel_size(self): - self._test(AvgPool2d(kernel_size=4)) + inputs = (torch.randn(1, 16, 24, 24),) + self._test(AvgPool2d(kernel_size=4), inputs) diff --git a/backends/samsung/test/ops/test_batch_norm.py b/backends/samsung/test/ops/test_batch_norm.py index 7cb9db0e47c..3c73f6d993a 100644 --- a/backends/samsung/test/ops/test_batch_norm.py +++ b/backends/samsung/test/ops/test_batch_norm.py @@ -43,6 +43,7 @@ def _test(self, module: torch.nn.Module, inputs): ) .check_count({"torch.ops.higher_order.executorch_call_delegate": 1}) .to_executorch() + .run_method_and_compare_outputs(inputs=inputs) ) def test_fp32_batch_norm(self): diff --git a/backends/samsung/test/ops/test_bmm.py b/backends/samsung/test/ops/test_bmm.py index 7712a50a0c9..f927b051603 100644 --- a/backends/samsung/test/ops/test_bmm.py +++ b/backends/samsung/test/ops/test_bmm.py @@ -31,6 +31,7 @@ def forward(self, x: torch.Tensor, y: torch.Tensor) -> torch.Tensor: class TestBatchMatmul(unittest.TestCase): def _test(self, module: torch.nn.Module): + torch.manual_seed(8) inputs = module.get_example_inputs() tester = SamsungTester( module, inputs, [gen_samsung_backend_compile_spec("E9955")] @@ -42,6 +43,7 @@ def _test(self, module: torch.nn.Module): .check_not(["executorch_exir_dialects_edge__ops_aten_bmm_default"]) .check_count({"torch.ops.higher_order.executorch_call_delegate": 1}) .to_executorch() + .run_method_and_compare_outputs(inputs=inputs, atol=0.005, rtol=0.005) ) def test_fp32_bmm(self): diff --git a/backends/samsung/test/ops/test_cat.py b/backends/samsung/test/ops/test_cat.py index f744f9ca882..a2d42370da5 100644 --- a/backends/samsung/test/ops/test_cat.py +++ b/backends/samsung/test/ops/test_cat.py @@ -37,6 +37,7 @@ def _test(self, module: torch.nn.Module, inputs): .check_not(["executorch_exir_dialects_edge__ops_aten_cat_default"]) .check_count({"torch.ops.higher_order.executorch_call_delegate": 1}) .to_executorch() + .run_method_and_compare_outputs(inputs=inputs) ) def test_fp32_concat_on_axis1(self): diff --git a/backends/samsung/test/ops/test_clamp.py b/backends/samsung/test/ops/test_clamp.py index 00e3eb72690..3c1ac40539b 100644 --- a/backends/samsung/test/ops/test_clamp.py +++ b/backends/samsung/test/ops/test_clamp.py @@ -42,6 +42,7 @@ def _test(self, module: torch.nn.Module, inputs): .check_not(["executorch_exir_dialects_edge__ops_aten_clamp_default"]) .check_count({"torch.ops.higher_order.executorch_call_delegate": 1}) .to_executorch() + .run_method_and_compare_outputs(inputs=inputs) ) def test_fp32_clamp(self): diff --git a/backends/samsung/test/ops/test_constant_pad_nd.py b/backends/samsung/test/ops/test_constant_pad_nd.py index dae24abb7d7..5c6c6e4376c 100644 --- a/backends/samsung/test/ops/test_constant_pad_nd.py +++ b/backends/samsung/test/ops/test_constant_pad_nd.py @@ -38,6 +38,7 @@ def _test(self, module: torch.nn.Module, inputs): ) .check_count({"torch.ops.higher_order.executorch_call_delegate": 1}) .to_executorch() + .run_method_and_compare_outputs(inputs=inputs) ) def test_fp32_constant_pad_nd(self): diff --git a/backends/samsung/test/ops/test_conv2d.py b/backends/samsung/test/ops/test_conv2d.py index e832a5713dc..39c2b2508e6 100644 --- a/backends/samsung/test/ops/test_conv2d.py +++ b/backends/samsung/test/ops/test_conv2d.py @@ -41,10 +41,6 @@ def __init__( self.in_channels = in_channels - def get_example_inputs(self) -> tuple[torch.Tensor]: - input_1 = torch.randn(1, self.in_channels, 24, 24) - return (input_1,) - def forward(self, x: torch.Tensor) -> torch.Tensor: return self.conv(x) @@ -62,19 +58,15 @@ def __init__(self) -> None: bias=True, ) - def get_example_inputs(self) -> tuple[torch.Tensor]: - input_1 = torch.randn(1, 32, 24, 24) - return (input_1,) - def forward(self, x: torch.Tensor) -> torch.Tensor: return self.conv(x) class TestConv2d(unittest.TestCase): - def _test(self, module: torch.nn.Module): + def _test(self, module: torch.nn.Module, inputs): tester = SamsungTester( module, - module.get_example_inputs(), + inputs, [gen_samsung_backend_compile_spec("E9955")], ) ( @@ -83,16 +75,21 @@ def _test(self, module: torch.nn.Module): .check_not(["executorch_exir_dialects_edge__ops_aten_convolution_default"]) .check_count({"torch.ops.higher_order.executorch_call_delegate": 1}) .to_executorch() + .run_method_and_compare_outputs(inputs=inputs) ) def test_fp32_conv2d_without_bias(self): - self._test(Conv2d(bias=False)) + inputs = (torch.randn(1, 3, 24, 24),) + self._test(Conv2d(bias=False), inputs) def test_fp32_conv2d_with_bias(self): - self._test(Conv2d(bias=True)) + inputs = (torch.randn(1, 3, 24, 24),) + self._test(Conv2d(bias=True), inputs) def test_fp32_depthwise_conv2d(self): - self._test(Conv2d(in_channels=8, out_channels=8, groups=8)) + inputs = (torch.randn(1, 8, 24, 24),) + self._test(Conv2d(in_channels=8, out_channels=8, groups=8), inputs) def test_fp32_transpose_conv2d(self): - self._test(TransposeConv2d()) + inputs = (torch.randn(1, 32, 24, 24),) + self._test(TransposeConv2d(), inputs) diff --git a/backends/samsung/test/ops/test_div.py b/backends/samsung/test/ops/test_div.py index 31384afd896..5a27531a96e 100644 --- a/backends/samsung/test/ops/test_div.py +++ b/backends/samsung/test/ops/test_div.py @@ -38,6 +38,7 @@ def _test(self, module: torch.nn.Module, inputs): .check_not(["executorch_exir_dialects_edge__ops_aten_div_Tensor"]) .check_count({"torch.ops.higher_order.executorch_call_delegate": 1}) .to_executorch() + .run_method_and_compare_outputs(inputs=inputs) ) def test_fp32_simple_div(self): diff --git a/backends/samsung/test/ops/test_embedding.py b/backends/samsung/test/ops/test_embedding.py index fb6aaaf7766..ca3899d4c24 100644 --- a/backends/samsung/test/ops/test_embedding.py +++ b/backends/samsung/test/ops/test_embedding.py @@ -37,9 +37,10 @@ def _test(self, module: torch.nn.Module, inputs): .check_not(["executorch_exir_dialects_edge__ops_aten_embedding_default"]) .check_count({"torch.ops.higher_order.executorch_call_delegate": 1}) .to_executorch() + .run_method_and_compare_outputs(inputs=inputs) ) def test_fp32_embedding(self): num_embeddings = 2048 - inputs = (torch.randint(0, num_embeddings, (1, 64), dtype=torch.int32),) + inputs = (torch.randint(0, 12, (1, 64), dtype=torch.int32),) self._test(Embedding(num_embeddings=num_embeddings), inputs) diff --git a/backends/samsung/test/ops/test_expand_copy.py b/backends/samsung/test/ops/test_expand_copy.py index 47df38be8e2..de0f36e03d0 100644 --- a/backends/samsung/test/ops/test_expand_copy.py +++ b/backends/samsung/test/ops/test_expand_copy.py @@ -38,6 +38,7 @@ def _test(self, module: torch.nn.Module, inputs): .check_not(["executorch_exir_dialects_edge__ops_aten_expand_copy_default"]) .check_count({"torch.ops.higher_order.executorch_call_delegate": 1}) .to_executorch() + .run_method_and_compare_outputs(inputs=inputs) ) def test_fp32_expand_copy(self): diff --git a/backends/samsung/test/ops/test_gelu.py b/backends/samsung/test/ops/test_gelu.py index 4e6f2d971ab..20f93559fda 100644 --- a/backends/samsung/test/ops/test_gelu.py +++ b/backends/samsung/test/ops/test_gelu.py @@ -53,6 +53,7 @@ def _test(self, module: torch.nn.Module, inputs): .check_not(["executorch_exir_dialects_edge__ops_aten_gelu_default"]) .check_count({"torch.ops.higher_order.executorch_call_delegate": 1}) .to_executorch() + .run_method_and_compare_outputs(atol=0.002, rtol=0.002) ) def test_fp32_single_gelu(self): diff --git a/backends/samsung/test/ops/test_leaky_relu.py b/backends/samsung/test/ops/test_leaky_relu.py index 0af6ea0da90..4ad510528f9 100644 --- a/backends/samsung/test/ops/test_leaky_relu.py +++ b/backends/samsung/test/ops/test_leaky_relu.py @@ -39,6 +39,7 @@ def _test(self, module: torch.nn.Module, inputs): .check_not(["executorch_exir_dialects_edge__ops_aten_leaky_relu_default"]) .check_count({"torch.ops.higher_order.executorch_call_delegate": 1}) .to_executorch() + .run_method_and_compare_outputs(inputs=inputs) ) def test_fp32_leaky_relu(self): diff --git a/backends/samsung/test/ops/test_linear.py b/backends/samsung/test/ops/test_linear.py index f327464fc0c..ce1f13d1a1f 100644 --- a/backends/samsung/test/ops/test_linear.py +++ b/backends/samsung/test/ops/test_linear.py @@ -39,7 +39,7 @@ def _test(self, module: torch.nn.Module, inputs): .check_not(["executorch_exir_dialects_edge__ops_aten_linear_default"]) .check_count({"torch.ops.higher_order.executorch_call_delegate": 1}) .to_executorch() - .run_method_and_compare_outputs() + .run_method_and_compare_outputs(inputs=inputs) ) def test_fp32_linear(self): diff --git a/backends/samsung/test/ops/test_log_softmax.py b/backends/samsung/test/ops/test_log_softmax.py index 2e2b3ff0604..2aeb600e977 100644 --- a/backends/samsung/test/ops/test_log_softmax.py +++ b/backends/samsung/test/ops/test_log_softmax.py @@ -38,6 +38,7 @@ def _test(self, module: torch.nn.Module, inputs): .check_not(["executorch_exir_dialects_edge__ops_aten__log_softmax_default"]) .check_count({"torch.ops.higher_order.executorch_call_delegate": 1}) .to_executorch() + .run_method_and_compare_outputs(inputs=inputs) ) def test_fp32_log_softmax(self): diff --git a/backends/samsung/test/ops/test_max_pool2d.py b/backends/samsung/test/ops/test_max_pool2d.py index 9ead91b2bff..d944c38a678 100644 --- a/backends/samsung/test/ops/test_max_pool2d.py +++ b/backends/samsung/test/ops/test_max_pool2d.py @@ -34,19 +34,15 @@ def __init__( ceil_mode=False, ).to(torch.float) - def get_example_inputs(self) -> tuple[torch.Tensor]: - input_1 = torch.randn(1, 16, 24, 24) - return (input_1,) - def forward(self, x: torch.Tensor) -> torch.Tensor: return self.max_pool(x) class TestMaxPool2d(unittest.TestCase): - def _test(self, module: torch.nn.Module): + def _test(self, module: torch.nn.Module, inputs): tester = SamsungTester( module, - module.get_example_inputs(), + inputs, [gen_samsung_backend_compile_spec("E9955")], ) ( @@ -56,16 +52,21 @@ def _test(self, module: torch.nn.Module): .check_not(["executorch_exir_dialects_edge__ops_aten_max_pool2d_default"]) .check_count({"torch.ops.higher_order.executorch_call_delegate": 1}) .to_executorch() + .run_method_and_compare_outputs(inputs=inputs) ) def test_fp32_max_pool2d(self): - self._test(MaxPool2d()) + inputs = (torch.randn(1, 16, 24, 24),) + self._test(MaxPool2d(), inputs) def test_fp32_max_pool2d_with_padding(self): - self._test(MaxPool2d(padding=1)) + inputs = (torch.randn(1, 16, 24, 24),) + self._test(MaxPool2d(padding=1), inputs) def test_fp32_max_pool2d_with_kernel_size(self): - self._test(MaxPool2d(kernel_size=4)) + inputs = (torch.randn(1, 16, 24, 24),) + self._test(MaxPool2d(kernel_size=4), inputs) def test_fp32_max_pool2d_with_dilation(self): - self._test(MaxPool2d(dilation=2)) + inputs = (torch.randn(1, 16, 24, 24),) + self._test(MaxPool2d(dilation=2), inputs) diff --git a/backends/samsung/test/ops/test_mean_dim.py b/backends/samsung/test/ops/test_mean_dim.py index 113e26c45b2..5c6378000bd 100644 --- a/backends/samsung/test/ops/test_mean_dim.py +++ b/backends/samsung/test/ops/test_mean_dim.py @@ -39,6 +39,7 @@ def _test(self, module: torch.nn.Module, inputs): .check_not(["executorch_exir_dialects_edge__ops_aten_mean_dim"]) .check_count({"torch.ops.higher_order.executorch_call_delegate": 1}) .to_executorch() + .run_method_and_compare_outputs(inputs=inputs) ) def test_fp32_mean_with_keep_dims(self): diff --git a/backends/samsung/test/ops/test_minimum.py b/backends/samsung/test/ops/test_minimum.py index de275cc4d46..e82b2e0c428 100644 --- a/backends/samsung/test/ops/test_minimum.py +++ b/backends/samsung/test/ops/test_minimum.py @@ -37,6 +37,7 @@ def _test(self, module: torch.nn.Module, inputs): .check_not(["executorch_exir_dialects_edge__ops_aten_minimum_default"]) .check_count({"torch.ops.higher_order.executorch_call_delegate": 1}) .to_executorch() + .run_method_and_compare_outputs(inputs=inputs) ) def test_fp32_minimum(self): diff --git a/backends/samsung/test/ops/test_mul.py b/backends/samsung/test/ops/test_mul.py index 57d13c68b87..0f77a5e8f55 100644 --- a/backends/samsung/test/ops/test_mul.py +++ b/backends/samsung/test/ops/test_mul.py @@ -47,6 +47,7 @@ def _test(self, module: torch.nn.Module, inputs): .check_not(["executorch_exir_dialects_edge__ops_aten_mul_Tensor"]) .check_count({"torch.ops.higher_order.executorch_call_delegate": 1}) .to_executorch() + .run_method_and_compare_outputs(inputs=inputs) ) def test_fp32_simple_mul(self): diff --git a/backends/samsung/test/ops/test_permute.py b/backends/samsung/test/ops/test_permute.py index 3889c803e85..e0052c3ec37 100644 --- a/backends/samsung/test/ops/test_permute.py +++ b/backends/samsung/test/ops/test_permute.py @@ -39,6 +39,7 @@ def _test(self, module: torch.nn.Module, inputs): .check_not(["executorch_exir_dialects_edge__ops_aten_permute_default"]) .check_count({"torch.ops.higher_order.executorch_call_delegate": 1}) .to_executorch() + .run_method_and_compare_outputs(inputs=inputs) ) def test_fp32_permute_0231(self): diff --git a/backends/samsung/test/ops/test_pixel_shuffle.py b/backends/samsung/test/ops/test_pixel_shuffle.py index bc7a53ff592..f7d86e5b1a9 100644 --- a/backends/samsung/test/ops/test_pixel_shuffle.py +++ b/backends/samsung/test/ops/test_pixel_shuffle.py @@ -41,6 +41,7 @@ def _test(self, module: torch.nn.Module, inputs): ) .check_count({"torch.ops.higher_order.executorch_call_delegate": 1}) .to_executorch() + .run_method_and_compare_outputs(inputs=inputs) ) def test_fp32_pixel_shuffle(self): diff --git a/backends/samsung/test/ops/test_relu.py b/backends/samsung/test/ops/test_relu.py index 386827109f0..20da52cb10f 100644 --- a/backends/samsung/test/ops/test_relu.py +++ b/backends/samsung/test/ops/test_relu.py @@ -53,6 +53,7 @@ def _test(self, module: torch.nn.Module, inputs): .check_not(["executorch_exir_dialects_edge__ops_aten_relu_default"]) .check_count({"torch.ops.higher_order.executorch_call_delegate": 1}) .to_executorch() + .run_method_and_compare_outputs(inputs=inputs) ) def test_fp32_single_relu(self): diff --git a/backends/samsung/test/ops/test_reshape.py b/backends/samsung/test/ops/test_reshape.py index 8c89d946361..148186fb997 100644 --- a/backends/samsung/test/ops/test_reshape.py +++ b/backends/samsung/test/ops/test_reshape.py @@ -38,6 +38,7 @@ def _test(self, module: torch.nn.Module, inputs): .check_not(["executorch_exir_dialects_edge__ops_aten_view_default"]) .check_count({"torch.ops.higher_order.executorch_call_delegate": 1}) .to_executorch() + .run_method_and_compare_outputs(inputs=inputs) ) def test_fp32_reshape(self): diff --git a/backends/samsung/test/ops/test_rsqrt.py b/backends/samsung/test/ops/test_rsqrt.py index 4bf302c867f..9cab9456d64 100644 --- a/backends/samsung/test/ops/test_rsqrt.py +++ b/backends/samsung/test/ops/test_rsqrt.py @@ -38,6 +38,7 @@ def _test(self, module: torch.nn.Module, inputs): .check_not(["executorch_exir_dialects_edge__ops_aten_rsqrt_default"]) .check_count({"torch.ops.higher_order.executorch_call_delegate": 1}) .to_executorch() + .run_method_and_compare_outputs(inputs=inputs) ) def test_fp32_rsqrt(self): diff --git a/backends/samsung/test/ops/test_select.py b/backends/samsung/test/ops/test_select.py index dcb0667d036..3d619f37a0f 100644 --- a/backends/samsung/test/ops/test_select.py +++ b/backends/samsung/test/ops/test_select.py @@ -40,6 +40,7 @@ def _test(self, module: torch.nn.Module, inputs): .check_not(["executorch_exir_dialects_edge__ops_aten_select_copy_int"]) .check_count({"torch.ops.higher_order.executorch_call_delegate": 1}) .to_executorch() + .run_method_and_compare_outputs(inputs=inputs) ) def test_fp32_select_on_axis1(self): diff --git a/backends/samsung/test/ops/test_slice_copy.py b/backends/samsung/test/ops/test_slice_copy.py index f31410b8a41..4b3a100f927 100644 --- a/backends/samsung/test/ops/test_slice_copy.py +++ b/backends/samsung/test/ops/test_slice_copy.py @@ -38,6 +38,7 @@ def _test(self, module: torch.nn.Module, inputs): .check_not(["executorch_exir_dialects_edge__ops_aten_slice_copy_Tensor"]) .check_count({"torch.ops.higher_order.executorch_call_delegate": 1}) .to_executorch() + .run_method_and_compare_outputs(inputs=inputs) ) def test_fp32_slice_copy(self): diff --git a/backends/samsung/test/ops/test_softmax.py b/backends/samsung/test/ops/test_softmax.py index a4c2f36acfc..8721df588d1 100644 --- a/backends/samsung/test/ops/test_softmax.py +++ b/backends/samsung/test/ops/test_softmax.py @@ -38,7 +38,7 @@ def _test(self, module: torch.nn.Module, inputs): .check_not(["executorch_exir_dialects_edge__ops_aten__softmax_default"]) .check_count({"torch.ops.higher_order.executorch_call_delegate": 1}) .to_executorch() - .run_method_and_compare_outputs() + .run_method_and_compare_outputs(inputs=inputs) ) def test_fp32_softmax(self): diff --git a/backends/samsung/test/ops/test_sqrt.py b/backends/samsung/test/ops/test_sqrt.py index e1a084c3611..1ed31277dc3 100644 --- a/backends/samsung/test/ops/test_sqrt.py +++ b/backends/samsung/test/ops/test_sqrt.py @@ -38,6 +38,7 @@ def _test(self, module: torch.nn.Module, inputs): .check_not(["executorch_exir_dialects_edge__ops_aten_sqrt_default"]) .check_count({"torch.ops.higher_order.executorch_call_delegate": 1}) .to_executorch() + .run_method_and_compare_outputs(inputs=inputs) ) def test_fp32_sqrt(self): diff --git a/backends/samsung/test/ops/test_squeeze.py b/backends/samsung/test/ops/test_squeeze.py index ab93758f203..329053adc8c 100644 --- a/backends/samsung/test/ops/test_squeeze.py +++ b/backends/samsung/test/ops/test_squeeze.py @@ -39,8 +39,9 @@ def _test(self, module: torch.nn.Module, inputs): .check_not(["executorch_exir_dialects_edge__ops_aten_squeeze_dims"]) .check_count({"torch.ops.higher_order.executorch_call_delegate": 1}) .to_executorch() + .run_method_and_compare_outputs(inputs=inputs) ) def test_fp32_squeeze(self): - inputs = (torch.randn(1, 2, 1, 3, 1),) - self._test(Squeeze(dims=[2, 4]), inputs) + inputs = (torch.randn(1, 2, 1, 3),) + self._test(Squeeze(dims=[2]), inputs) diff --git a/backends/samsung/test/ops/test_sub.py b/backends/samsung/test/ops/test_sub.py index 5541a52c80c..aea428b34b8 100644 --- a/backends/samsung/test/ops/test_sub.py +++ b/backends/samsung/test/ops/test_sub.py @@ -47,6 +47,7 @@ def _test(self, module: torch.nn.Module, inputs): .check_not(["executorch_exir_dialects_edge__ops_aten_sub_Tensor"]) .check_count({"torch.ops.higher_order.executorch_call_delegate": 1}) .to_executorch() + .run_method_and_compare_outputs(inputs=inputs) ) def test_fp32_simple_sub(self): diff --git a/backends/samsung/test/ops/test_to_copy.py b/backends/samsung/test/ops/test_to_copy.py index d6917c9403f..002e85801fe 100644 --- a/backends/samsung/test/ops/test_to_copy.py +++ b/backends/samsung/test/ops/test_to_copy.py @@ -41,6 +41,7 @@ def _test(self, module: torch.nn.Module, inputs): ) .check_count({"torch.ops.higher_order.executorch_call_delegate": 1}) .to_executorch() + .run_method_and_compare_outputs(inputs=inputs) ) def test_fp32_to_copy(self): diff --git a/backends/samsung/test/ops/test_unsqueeze.py b/backends/samsung/test/ops/test_unsqueeze.py index 543fa0bc282..e10745fa839 100644 --- a/backends/samsung/test/ops/test_unsqueeze.py +++ b/backends/samsung/test/ops/test_unsqueeze.py @@ -21,10 +21,6 @@ def __init__(self, axis) -> None: super().__init__() self.axis = axis - def get_example_inputs(self) -> tuple[torch.Tensor]: - input_1 = torch.randn(2, 3, 1, 4) # input should be positive - return (input_1,) - def forward(self, x: torch.Tensor) -> torch.Tensor: return torch.unsqueeze(x, dim=self.axis) @@ -43,6 +39,7 @@ def _test(self, module: torch.nn.Module, inputs): .check_not(["executorch_exir_dialects_edge__ops_aten_unsqueeze_default"]) .check_count({"torch.ops.higher_order.executorch_call_delegate": 1}) .to_executorch() + .run_method_and_compare_outputs(inputs=inputs) ) def test_fp32_unsqueeze(self): diff --git a/backends/samsung/test/ops/test_upsample_bilinear2d.py b/backends/samsung/test/ops/test_upsample_bilinear2d.py index 37dcb28df83..7bdf3ab4041 100644 --- a/backends/samsung/test/ops/test_upsample_bilinear2d.py +++ b/backends/samsung/test/ops/test_upsample_bilinear2d.py @@ -46,6 +46,7 @@ def _test(self, module: torch.nn.Module, inputs): ) .check_count({"torch.ops.higher_order.executorch_call_delegate": 1}) .to_executorch() + .run_method_and_compare_outputs(inputs=inputs) ) def test_fp32_upsample_bilinear2d(self): diff --git a/backends/samsung/test/ops/test_upsample_nearest2d.py b/backends/samsung/test/ops/test_upsample_nearest2d.py index e027ab23337..bbdff40a0e9 100644 --- a/backends/samsung/test/ops/test_upsample_nearest2d.py +++ b/backends/samsung/test/ops/test_upsample_nearest2d.py @@ -45,6 +45,7 @@ def _test(self, module: torch.nn.Module, inputs): ) .check_count({"torch.ops.higher_order.executorch_call_delegate": 1}) .to_executorch() + .run_method_and_compare_outputs(inputs=inputs) ) def test_fp32_upsample_nearest2d(self): diff --git a/backends/samsung/test/tester/samsung_tester.py b/backends/samsung/test/tester/samsung_tester.py index 1a595d5d77a..f33d508dfca 100644 --- a/backends/samsung/test/tester/samsung_tester.py +++ b/backends/samsung/test/tester/samsung_tester.py @@ -4,26 +4,49 @@ # Licensed under the BSD License (the "License"); you may not use this file # except in compliance with the License. See the license file in the root # directory of this source tree for more details. -from typing import List, Optional, Tuple +import copy +from typing import Any, List, Optional, Sequence, Tuple, Union import executorch.backends.test.harness.stages as BaseStages import torch from executorch.backends.samsung.partition.enn_partitioner import EnnPartitioner -from executorch.backends.samsung.utils.export_utils import get_edge_compile_config - +from executorch.backends.samsung.quantizer.quantizer import EnnQuantizer, Precision +from executorch.backends.samsung.test.utils import RuntimeExecutor +from executorch.backends.samsung.utils.export_utils import ( + get_edge_compile_config, + get_enn_pass_list, +) from executorch.backends.test.harness import Tester as TesterBase +from executorch.backends.test.harness.stages import StageType from executorch.exir import EdgeCompileConfig, to_edge_transform_and_lower from executorch.exir.backend.backend_details import CompileSpec +from executorch.exir.pass_manager import PassType from torch.export import ExportedProgram +from torchao.quantization.pt2e.quantizer import Quantizer + class Export(BaseStages.Export): pass class Quantize(BaseStages.Quantize): - pass + def __init__( + self, + quantizer: Optional[Quantizer] = None, + quantization_config: Optional[Any] = None, + calibrate: bool = True, + calibration_samples: Optional[Sequence[Any]] = None, + is_qat: Optional[bool] = False, + ): + super().__init__( + quantizer=quantizer, + quantization_config=quantization_config, + calibrate=calibrate, + calibration_samples=calibration_samples, + is_qat=is_qat, + ) class ToEdgeTransformAndLower(BaseStages.ToEdgeTransformAndLower): @@ -31,24 +54,30 @@ def __init__( self, compile_specs: Optional[List[CompileSpec]] = None, edge_compile_config: Optional[EdgeCompileConfig] = None, + transform_passes: Optional[Union[Sequence[PassType]]] = None, ): compile_specs = compile_specs or [] self.partitioners = [EnnPartitioner(compile_specs=compile_specs)] self.edge_compile_config = edge_compile_config or get_edge_compile_config() + self.transform_passes = transform_passes or get_enn_pass_list() self.edge_dialect_program = None def run( self, artifact: ExportedProgram, inputs=None, generate_etrecord: bool = False ) -> None: + artifact_copy = copy.deepcopy(artifact) self.edge_dialect_program = to_edge_transform_and_lower( - artifact, + artifact_copy, + transform_passes=self.transform_passes, partitioner=self.partitioners, compile_config=self.edge_compile_config, ) class ToExecutorch(BaseStages.ToExecutorch): - pass + def run_artifact(self, inputs): + runtime_executor = RuntimeExecutor(self.artifact, inputs) + return runtime_executor.run_on_device() class SamsungTester(TesterBase): @@ -60,8 +89,14 @@ def __init__( ): module.eval() + stage_classes = TesterBase.default_stage_classes() | { + StageType.EXPORT: Export, + StageType.TO_EXECUTORCH: ToExecutorch, + } + super().__init__( module=module, + stage_classes=stage_classes, example_inputs=example_inputs, dynamic_shapes=None, ) @@ -71,6 +106,14 @@ def __init__( self.example_inputs = example_inputs self.compile_specs = compile_specs + def quantize(self, quantize_stage: Optional[Quantize] = None): + if quantize_stage is None: + quantizer = EnnQuantizer() + quantizer.setup_quant_params(Precision.A8W8) + quantize_stage = Quantize(quantizer) + + return super().quantize(quantize_stage) + def to_edge_transform_and_lower( self, edge_compile_config: Optional[EdgeCompileConfig] = None, diff --git a/backends/samsung/test/utils/__init__.py b/backends/samsung/test/utils/__init__.py new file mode 100644 index 00000000000..87f61537416 --- /dev/null +++ b/backends/samsung/test/utils/__init__.py @@ -0,0 +1,11 @@ +# Copyright (c) Samsung Electronics Co. LTD +# All rights reserved +# +# Licensed under the BSD License (the "License"); you may not use this file +# except in compliance with the License. See the license file in the root +# directory of this source tree for more details. + +from .runtime_executor import RuntimeExecutor + + +__all__ = ["RuntimeExecutor"] diff --git a/backends/samsung/test/utils/runtime_executor.py b/backends/samsung/test/utils/runtime_executor.py new file mode 100644 index 00000000000..9bc274799d7 --- /dev/null +++ b/backends/samsung/test/utils/runtime_executor.py @@ -0,0 +1,186 @@ +import logging +import os +import subprocess +import tempfile +import time + +from functools import cache +from pathlib import Path +from typing import List, Tuple + +import numpy as np +import torch + + +logging.basicConfig( + level=logging.INFO, + format="%(asctime)s %(levelname)-8s %(message)s", + datefmt="%Y-%m-%d %H:%M:%S", +) + + +@cache +def get_runner_path() -> Path: + git_root = subprocess.check_output( + ["git", "rev-parse", "--show-toplevel"], + cwd=os.path.dirname(os.path.realpath(__file__)), + text=True, + ).strip() + return Path(git_root) / "build_samsung_android/backends/samsung/enn_executor_runner" + + +class EDBTestManager: + def __init__( + self, + pte_file, + work_directory, + input_files: List[str], + ): + self.pte_file = pte_file + self.work_directory = work_directory + self.input_files = input_files + self.artifacts_dir = Path(self.pte_file).parent.absolute() + self.output_folder = f"{self.work_directory}/output" + self.runner = str(get_runner_path()) + self.devicefarm = "devicefarm-cli" + + def _edb(self, cmd): + cmds = [self.devicefarm] + + cmds.extend(cmd) + command = " ".join(cmds) + + logging.info(f"[EDB] Run: {command}") + + result = subprocess.run( + command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE + ) + + stdout_msg = result.stdout.decode("utf-8").strip() + if stdout_msg: + logging.info(f"[EDB] stdout: {stdout_msg}") + + if result.returncode != 0: + stderr_msg = result.stderr.decode("utf-8").strip() + if stderr_msg: + logging.error(f"[EDB] stderr: {stderr_msg}") + raise RuntimeError("edb(Exynos device bridge) command execute failed") + else: + logging.info("[EDB] Command succeeded") + + def push(self): + self._edb(["-E", f"'rm -rf {self.work_directory}'"]) + self._edb(["-E", f"'mkdir -p {self.work_directory}'"]) + self._edb(["-U", self.pte_file, self.work_directory]) + self._edb(["-U", self.runner, self.work_directory]) + self._edb(["-E", f"'ls -al {self.work_directory}'"]) # temp + + for input_file in self.input_files: + input_file_path = os.path.join(self.artifacts_dir, input_file) + if Path(input_file).name == input_file and os.path.isfile(input_file_path): + # default search the same level directory with pte + self._edb(["-U", input_file_path, self.work_directory]) + elif os.path.isfile(input_file): + self._edb(["-U", input_file, self.work_directory]) + else: + raise FileNotFoundError(f"Invalid input file path: {input_file}") + + def execute(self): + self._edb(["-E", f"'rm -rf {self.output_folder}'"]) + self._edb(["-E", f"'mkdir -p {self.output_folder}'"]) + self._edb(["-E", f"'chmod 777 {self.output_folder}'"]) + # run the delegation + input_files_list = " ".join([os.path.basename(x) for x in self.input_files]) + enn_executor_runner_args = " ".join( + [ + f"--model {os.path.basename(self.pte_file)}", + f'--input "{input_files_list}"', + f"--output_path {self.output_folder}", + ] + ) + enn_executor_runner_cmd = " ".join( + [ + f"'cd {self.work_directory} &&", + "chmod +x ./enn_executor_runner &&", + f"./enn_executor_runner {enn_executor_runner_args}'", + ] + ) + + self._edb(["-E", f"{enn_executor_runner_cmd}"]) + + def pull(self, output_path): + self._edb(["-E", f"'ls -al {self.output_folder}'"]) + self._edb(["-D", self.output_folder, output_path]) + + +class RuntimeExecutor: + def __init__(self, executorch_program, inputs): + self.executorch_program = executorch_program + self.inputs = inputs + + def run_on_device(self) -> Tuple[torch.Tensor]: + with tempfile.TemporaryDirectory() as tmp_dir: + pte_filename, input_files = self._save_model_and_inputs(tmp_dir) + test_manager = EDBTestManager( + pte_file=os.path.join(tmp_dir, pte_filename), + work_directory="/data/local/tmp/enn-executorch-test", + input_files=input_files, + ) + test_manager.push() + test_manager.execute() + + host_output_save_dir = os.path.join(tmp_dir, "output") + test_manager.pull(host_output_save_dir) + + model_outputs = self._get_model_outputs() + + attempts = 0 + while attempts < 3: + if not os.path.isdir(host_output_save_dir): + attempts += 1 + time.sleep(0.5) + else: + break + + target_output_save_dir = os.path.join(host_output_save_dir, "output") + num_of_output_files = len(os.listdir(target_output_save_dir)) + assert num_of_output_files == len( + model_outputs + ), f"Number of outputs is invalid, expect {len(model_outputs)} while got {num_of_output_files}" + + result = [] + for idx in range(num_of_output_files): + output_array = np.fromfile( + os.path.join(target_output_save_dir, f"output_{idx}.bin"), + dtype=np.uint8, + ) + output_tensor = ( + torch.from_numpy(output_array) + .view(dtype=model_outputs[idx].dtype) + .view(*model_outputs[idx].shape) + ) + result.append(output_tensor) + + return tuple(result) + + def _get_model_outputs(self): + output_node = self.executorch_program.exported_program().graph.output_node() + output_fake_tensors = [] + for ori_output in output_node.args[0]: + output_fake_tensors.append(ori_output.meta["val"]) + + return tuple(output_fake_tensors) + + def _save_model_and_inputs(self, save_dir): + pte_file_name = "program.pte" + file_path = os.path.join(save_dir, f"{pte_file_name}") + with open(file_path, "wb") as file: + self.executorch_program.write_to_file(file) + + inputs_files = [] + for idx, input in enumerate(self.inputs): + input_file_name = f"input_{idx}.bin" + input.detach().numpy().tofile(os.path.join(save_dir, input_file_name)) + inputs_files.append(input_file_name) + + return pte_file_name, inputs_files diff --git a/backends/test/harness/tester.py b/backends/test/harness/tester.py index 02c6fc4c82d..bfcee310b69 100644 --- a/backends/test/harness/tester.py +++ b/backends/test/harness/tester.py @@ -418,6 +418,8 @@ def _compare_outputs( # Wrap both outputs as tuple, since executor output is always a tuple even if single tensor if isinstance(reference_output, torch.Tensor): reference_output = (reference_output,) + elif isinstance(reference_output, OrderedDict): + reference_output = tuple(reference_output.values()) if isinstance(stage_output, torch.Tensor): stage_output = (stage_output,)